This is an automated email from the ASF dual-hosted git repository.
diqiu50 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 eded01901 [#4757]feat(trino-connector): Support more partition and
sort order features of the Iceberg catalog (#4925)
eded01901 is described below
commit eded019014bcb85763bef6bd33ed17fb0fd235e1
Author: Yuhui <[email protected]>
AuthorDate: Tue Oct 22 17:03:45 2024 +0800
[#4757]feat(trino-connector): Support more partition and sort order
features of the Iceberg catalog (#4925)
### What changes were proposed in this pull request?
Support Iceberg partition expressions like: year(x), month(x), day(x),
hour(x), bucket(x, n), truncate(x,n)
Support Iceberg sort order expressions like: field DESC, field ASC,
field DESC NULLS FIRST, field ASC NULLS LAST
### Why are the changes needed?
Fix: #4757
### Does this PR introduce _any_ user-facing change?
NO
### How was this patch tested?
New UTs and ITs
---
trino-connector/integration-test/build.gradle.kts | 22 +-
.../integration/test/CloseableGroupTest.java | 57 ----
.../integration/test/TrinoQueryTestTool.java | 2 +-
.../00005_partition_sort_order.sql | 63 +++++
.../00005_partition_sort_order.txt | 71 +++++
.../trino-test-tools/trino_test.sh | 4 +-
.../trino/connector/GravitinoErrorCode.java | 2 +-
.../connector/catalog/iceberg/ExpressionUtil.java | 308 ++++++++++++++++++++
.../catalog/iceberg/IcebergMetadataAdapter.java | 35 +--
.../catalog/iceberg/TestExpressionUtil.java | 311 +++++++++++++++++++++
10 files changed, 776 insertions(+), 99 deletions(-)
diff --git a/trino-connector/integration-test/build.gradle.kts
b/trino-connector/integration-test/build.gradle.kts
index d4733d136..a238cd60e 100644
--- a/trino-connector/integration-test/build.gradle.kts
+++ b/trino-connector/integration-test/build.gradle.kts
@@ -70,17 +70,24 @@ dependencies {
testRuntimeOnly(libs.junit.jupiter.engine)
}
+tasks.register("setupDependencies") {
+ dependsOn(":trino-connector:trino-connector:jar")
+ dependsOn(":catalogs:catalog-lakehouse-iceberg:jar",
":catalogs:catalog-lakehouse-iceberg:runtimeJars")
+ dependsOn(":catalogs:catalog-jdbc-mysql:jar",
":catalogs:catalog-jdbc-mysql:runtimeJars")
+ dependsOn(":catalogs:catalog-jdbc-postgresql:jar",
":catalogs:catalog-jdbc-postgresql:runtimeJars")
+ dependsOn(":catalogs:catalog-hive:jar", ":catalogs:catalog-hive:runtimeJars")
+}
+
+tasks.build {
+ dependsOn("setupDependencies")
+}
+
tasks.test {
val skipITs = project.hasProperty("skipITs")
if (skipITs) {
exclude("**/integration/test/**")
} else {
- dependsOn(":trino-connector:trino-connector:jar")
- dependsOn(":catalogs:catalog-lakehouse-iceberg:jar",
":catalogs:catalog-lakehouse-iceberg:runtimeJars")
- dependsOn(":catalogs:catalog-jdbc-mysql:jar",
":catalogs:catalog-jdbc-mysql:runtimeJars")
- dependsOn(":catalogs:catalog-jdbc-postgresql:jar",
":catalogs:catalog-jdbc-postgresql:runtimeJars")
- dependsOn(":catalogs:catalog-hive:jar",
":catalogs:catalog-hive:runtimeJars")
-
+ dependsOn("setupDependencies")
doFirst {
copy {
from("${project.rootDir}/dev/docker/trino/conf")
@@ -113,8 +120,9 @@ tasks.test {
}
tasks.register<JavaExec>("TrinoTest") {
+ dependsOn("build")
classpath = sourceSets["test"].runtimeClasspath
-
mainClass.set("org.apache.gravitino.integration.test.trino.TrinoQueryTestTool")
+
mainClass.set("org.apache.gravitino.trino.connector.integration.test.TrinoQueryTestTool")
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
jvmArgs = listOf(
diff --git
a/trino-connector/integration-test/src/test/java/org/apache/gravitino/trino/connector/integration/test/CloseableGroupTest.java
b/trino-connector/integration-test/src/test/java/org/apache/gravitino/trino/connector/integration/test/CloseableGroupTest.java
deleted file mode 100644
index 75402108b..000000000
---
a/trino-connector/integration-test/src/test/java/org/apache/gravitino/trino/connector/integration/test/CloseableGroupTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.gravitino.trino.connector.integration.test;
-
-import java.io.Closeable;
-import org.apache.gravitino.integration.test.util.CloseableGroup;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
-
-public class CloseableGroupTest {
- @Test
- public void callCloseableTest() throws Exception {
- Closeable closeable1 = Mockito.mock(Closeable.class);
- Closeable closeable2 = Mockito.mock(Closeable.class);
- Closeable closeable3 = Mockito.mock(Closeable.class);
-
- CloseableGroup closeableGroup = CloseableGroup.create();
- closeableGroup.register(closeable1);
- closeableGroup.register(closeable2);
- closeableGroup.register(closeable3);
-
- closeableGroup.close();
- Mockito.verify(closeable1).close();
- Mockito.verify(closeable2).close();
- Mockito.verify(closeable3).close();
- }
-
- @Test
- public void callAutoCloseableTest() throws Exception {
- Closeable closeable1 = Mockito.mock(Closeable.class);
- AutoCloseable closeable2 = Mockito.mock(AutoCloseable.class);
-
- CloseableGroup closeableGroup = CloseableGroup.create();
- closeableGroup.register(closeable1);
- closeableGroup.register(closeable2);
-
- closeableGroup.close();
- Mockito.verify(closeable1).close();
- Mockito.verify(closeable2).close();
- }
-}
diff --git
a/trino-connector/integration-test/src/test/java/org/apache/gravitino/trino/connector/integration/test/TrinoQueryTestTool.java
b/trino-connector/integration-test/src/test/java/org/apache/gravitino/trino/connector/integration/test/TrinoQueryTestTool.java
index dee82d6a3..eeb56c0d4 100644
---
a/trino-connector/integration-test/src/test/java/org/apache/gravitino/trino/connector/integration/test/TrinoQueryTestTool.java
+++
b/trino-connector/integration-test/src/test/java/org/apache/gravitino/trino/connector/integration/test/TrinoQueryTestTool.java
@@ -185,7 +185,7 @@ public class TrinoQueryTestTool {
if (testSetsDir.isEmpty()) {
testSetsDir =
TrinoQueryIT.class.getClassLoader().getResource("trino-ci-testset").getPath();
- testSetsDir = ITUtils.joinPath(testSetsDir,
"trino-ci-testset/testsets");
+ testSetsDir = ITUtils.joinPath(testSetsDir, "testsets");
} else {
TrinoQueryIT.testsetsDir = testSetsDir;
}
diff --git
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/lakehouse-iceberg/00005_partition_sort_order.sql
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/lakehouse-iceberg/00005_partition_sort_order.sql
new file mode 100644
index 000000000..e3a353f09
--- /dev/null
+++
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/lakehouse-iceberg/00005_partition_sort_order.sql
@@ -0,0 +1,63 @@
+CREATE SCHEMA gt_db2;
+
+USE gt_db2;
+
+CREATE TABLE lineitem(
+ orderkey bigint,
+ partkey bigint,
+ suppkey bigint,
+ linenumber integer,
+ quantity decimal(12, 2),
+ extendedprice decimal(12, 2),
+ discount decimal(12, 2),
+ tax decimal(12, 2),
+ returnflag varchar,
+ linestatus varchar,
+ shipdate date,
+ commitdate date,
+ receiptdate date,
+ shipinstruct varchar,
+ shipmode varchar,
+ comment varchar
+)
+WITH (
+ partitioning = ARRAY['year(commitdate)'],
+ sorted_by = ARRAY['partkey', 'extendedprice desc']
+);
+
+show create table lineitem;
+
+insert into lineitem select * from tpch.tiny.lineitem;
+
+select * from lineitem order by orderkey, partkey limit 5;
+
+CREATE TABLE tb01(
+ orderkey bigint,
+ partkey bigint,
+ suppkey bigint,
+ linenumber integer,
+ quantity decimal(12, 2),
+ extendedprice decimal(12, 2),
+ discount decimal(12, 2),
+ tax decimal(12, 2),
+ returnflag varchar,
+ linestatus varchar,
+ shipdate date,
+ commitdate date,
+ receiptdate date,
+ shipinstruct varchar,
+ shipmode varchar,
+ comment varchar
+)
+WITH (
+ partitioning = ARRAY['day(commitdate)', 'month(shipdate)',
'bucket(partkey, 2)', 'truncate(shipinstruct, 2)'],
+ sorted_by = ARRAY['partkey asc nulls last', 'extendedprice DESC NULLS
FIRST']
+);
+
+show create table tb01;
+
+drop table tb01;
+
+drop table lineitem;
+
+drop schema gt_db2;
diff --git
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/lakehouse-iceberg/00005_partition_sort_order.txt
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/lakehouse-iceberg/00005_partition_sort_order.txt
new file mode 100644
index 000000000..429ae871c
--- /dev/null
+++
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/lakehouse-iceberg/00005_partition_sort_order.txt
@@ -0,0 +1,71 @@
+CREATE SCHEMA
+
+USE
+
+CREATE TABLE
+
+"CREATE TABLE %.gt_db2.lineitem (
+ orderkey bigint,
+ partkey bigint,
+ suppkey bigint,
+ linenumber integer,
+ quantity decimal(12, 2),
+ extendedprice decimal(12, 2),
+ discount decimal(12, 2),
+ tax decimal(12, 2),
+ returnflag varchar,
+ linestatus varchar,
+ shipdate date,
+ commitdate date,
+ receiptdate date,
+ shipinstruct varchar,
+ shipmode varchar,
+ comment varchar
+)
+COMMENT ''
+WITH (
+ location = 'hdfs://%/user/iceberg/warehouse/TrinoQueryIT/gt_db2%/lineitem',
+ partitioning = ARRAY['year(commitdate)'],
+ sorted_by = ARRAY['partkey','extendedprice DESC']
+)"
+
+INSERT: 60175 rows
+
+"1","22","48","4","28.00","25816.56","0.09","0.06","N","O","1996-04-21","1996-03-30","1996-05-16","NONE","AIR","lites.
fluffily even de"
+"1","157","10","6","32.00","33828.80","0.07","0.02","N","O","1996-01-30","1996-02-07","1996-02-03","DELIVER
IN PERSON","MAIL","arefully slyly ex"
+"1","241","23","5","24.00","27389.76","0.10","0.04","N","O","1996-03-30","1996-03-14","1996-04-01","NONE","FOB","
pending foxes. slyly re"
+"1","637","38","3","8.00","12301.04","0.10","0.02","N","O","1996-01-29","1996-03-05","1996-01-31","TAKE
BACK RETURN","REG AIR","riously. regular, express dep"
+"1","674","75","2","36.00","56688.12","0.09","0.06","N","O","1996-04-12","1996-02-28","1996-04-20","TAKE
BACK RETURN","MAIL","ly final dependencies: slyly bold "
+
+CREATE TABLE
+
+"CREATE TABLE %.gt_db2.tb01 (
+ orderkey bigint,
+ partkey bigint,
+ suppkey bigint,
+ linenumber integer,
+ quantity decimal(12, 2),
+ extendedprice decimal(12, 2),
+ discount decimal(12, 2),
+ tax decimal(12, 2),
+ returnflag varchar,
+ linestatus varchar,
+ shipdate date,
+ commitdate date,
+ receiptdate date,
+ shipinstruct varchar,
+ shipmode varchar,
+ comment varchar
+)
+COMMENT ''
+WITH (
+ location = 'hdfs://%/user/iceberg/warehouse/TrinoQueryIT/gt_db2%/tb01',
+ partitioning = ARRAY['day(commitdate)','month(shipdate)','bucket(partkey,
2)','truncate(shipinstruct, 2)'],
+ sorted_by = ARRAY['partkey ASC NULLS LAST','extendedprice DESC NULLS FIRST']
+)"
+
+DROP TABLE
+
+DROP TABLE
+
+DROP SCHEMA
\ No newline at end of file
diff --git a/trino-connector/integration-test/trino-test-tools/trino_test.sh
b/trino-connector/integration-test/trino-test-tools/trino_test.sh
index 012adc127..d74e45ac3 100755
--- a/trino-connector/integration-test/trino-test-tools/trino_test.sh
+++ b/trino-connector/integration-test/trino-test-tools/trino_test.sh
@@ -19,11 +19,11 @@
set -e
-DIR=$(cd "$(dirname "$0")" && pwd)/../..
+DIR=$(cd "$(dirname "$0")" && pwd)/../../..
export GRAVITINO_ROOT_DIR=$(cd "$DIR" && pwd)
export GRAVITINO_HOME=$GRAVITINO_ROOT_DIR
export GRAVITINO_TEST=true
-export HADOOP_USER_NAME=root
+export HADOOP_USER_NAME=anonymous
echo $GRAVITINO_ROOT_DIR
cd $GRAVITINO_ROOT_DIR
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
index b79145e95..5741e4427 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
@@ -50,7 +50,7 @@ public enum GravitinoErrorCode implements ErrorCodeSupplier {
GRAVITINO_OPERATION_FAILED(22, EXTERNAL),
GRAVITINO_RUNTIME_ERROR(23, EXTERNAL),
GRAVITINO_DUPLICATED_CATALOGS(24, EXTERNAL),
- ;
+ GRAVITINO_EXPRESSION_ERROR(25, EXTERNAL);
// suppress ImmutableEnumChecker because ErrorCode is outside the project.
@SuppressWarnings("ImmutableEnumChecker")
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/iceberg/ExpressionUtil.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/iceberg/ExpressionUtil.java
new file mode 100644
index 000000000..2ee253e27
--- /dev/null
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/iceberg/ExpressionUtil.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.trino.connector.catalog.iceberg;
+
+import static
org.apache.gravitino.trino.connector.GravitinoErrorCode.GRAVITINO_EXPRESSION_ERROR;
+
+import io.trino.spi.TrinoException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.expressions.NamedReference;
+import org.apache.gravitino.rel.expressions.literals.Literal;
+import org.apache.gravitino.rel.expressions.sorts.NullOrdering;
+import org.apache.gravitino.rel.expressions.sorts.SortDirection;
+import org.apache.gravitino.rel.expressions.sorts.SortOrder;
+import org.apache.gravitino.rel.expressions.sorts.SortOrders;
+import org.apache.gravitino.rel.expressions.transforms.Transform;
+import org.apache.gravitino.rel.expressions.transforms.Transforms;
+
+/** This class is used to convert expression of bucket, sort_by, partition
object to string */
+public class ExpressionUtil {
+ private static final String IDENTIFIER = "[a-zA-Z_][a-zA-Z0-9_]*";
+ private static final String FUNCTION_ARG_INT = "(\\d+)";
+ private static final String FUNCTION_ARG_IDENTIFIER = "(" + IDENTIFIER + ")";
+ private static final Pattern YEAR_FUNCTION_PATTERN =
+ Pattern.compile("year\\(" + FUNCTION_ARG_IDENTIFIER + "\\)",
Pattern.CASE_INSENSITIVE);
+ private static final Pattern MONTH_FUNCTION_PATTERN =
+ Pattern.compile("month\\(" + FUNCTION_ARG_IDENTIFIER + "\\)",
Pattern.CASE_INSENSITIVE);
+ private static final Pattern DAY_FUNCTION_PATTERN =
+ Pattern.compile("day\\(" + FUNCTION_ARG_IDENTIFIER + "\\)",
Pattern.CASE_INSENSITIVE);
+ private static final Pattern HOUR_FUNCTION_PATTERN =
+ Pattern.compile("hour\\(" + FUNCTION_ARG_IDENTIFIER + "\\)",
Pattern.CASE_INSENSITIVE);
+ private static final Pattern BUCKET_FUNCTION_PATTERN =
+ Pattern.compile(
+ "bucket\\(" + FUNCTION_ARG_IDENTIFIER + ",\\s*" + FUNCTION_ARG_INT +
"\\)",
+ Pattern.CASE_INSENSITIVE);
+ private static final Pattern TRUNCATE_FUNCTION_PATTERN =
+ Pattern.compile(
+ "truncate\\(" + FUNCTION_ARG_IDENTIFIER + ",\\s*" + FUNCTION_ARG_INT
+ "\\)",
+ Pattern.CASE_INSENSITIVE);
+ private static final Pattern IDENTIFILER_PATTERN =
+ Pattern.compile(IDENTIFIER, Pattern.CASE_INSENSITIVE);
+
+ private static final String SORT_DIRECTION_ASC = "ASC";
+ private static final String SORT_DIRECTION_DESC = "DESC";
+ private static final String NULL_ORDERING_FIRST = "NULLS FIRST";
+ private static final String NULL_ORDERING_LAST = "NULLS LAST";
+ private static final String SORT_DIRECTION =
+ "(" + SORT_DIRECTION_ASC + "|" + SORT_DIRECTION_DESC + ")";
+ private static final String NULL_ORDERING =
+ "(" + NULL_ORDERING_FIRST + "|" + NULL_ORDERING_LAST + ")";
+ private static final Pattern SROT_ORDER_PATTERN =
+ Pattern.compile(IDENTIFIER, Pattern.CASE_INSENSITIVE);
+ private static final Pattern SORT_ORDER_WITH_SORT_DIRECTION_PATTERN =
+ Pattern.compile("(" + IDENTIFIER + ")\\s+" + SORT_DIRECTION,
Pattern.CASE_INSENSITIVE);
+ private static final Pattern
SORT_ORDER_WITH_SORT_DIRECTION_AND_NULL_ORDERING_PATTERN =
+ Pattern.compile(
+ "(" + IDENTIFIER + ")\\s+" + SORT_DIRECTION + "\\s+" + NULL_ORDERING,
+ Pattern.CASE_INSENSITIVE);
+
+ public static List<String> expressionToPartitionFiled(Transform[]
transforms) {
+ try {
+ List<String> partitionFields = new ArrayList<>();
+ for (Transform transform : transforms) {
+ partitionFields.add(transFormToString(transform));
+ }
+ return partitionFields;
+ } catch (IllegalArgumentException e) {
+ throw new TrinoException(
+ GRAVITINO_EXPRESSION_ERROR,
+ "Error to handle transform Expressions :" + e.getMessage(),
+ e);
+ }
+ }
+
+ public static Transform[] partitionFiledToExpression(List<String>
partitions) {
+ try {
+ List<Transform> partitionTransforms = new ArrayList<>();
+ for (String partition : partitions) {
+ parseTransform(partitionTransforms, partition);
+ }
+ return partitionTransforms.toArray(new Transform[0]);
+ } catch (IllegalArgumentException e) {
+ throw new TrinoException(
+ GRAVITINO_EXPRESSION_ERROR, "Error parsing the partition field: " +
e.getMessage(), e);
+ }
+ }
+
+ public static List<String> expressionToSortOrderFiled(SortOrder[] orders) {
+ try {
+ List<String> orderFields = new ArrayList<>();
+ for (SortOrder order : orders) {
+ orderFields.add(sortOrderToString(order));
+ }
+ return orderFields;
+ } catch (IllegalArgumentException e) {
+ throw new TrinoException(
+ GRAVITINO_EXPRESSION_ERROR,
+ "Error to handle the sort order expressions : " + e.getMessage(),
+ e);
+ }
+ }
+
+ public static SortOrder[] sortOrderFiledToExpression(List<String>
orderFields) {
+ try {
+ List<SortOrder> sortOrders = new ArrayList<>();
+ for (String orderField : orderFields) {
+ parseSortOrder(sortOrders, orderField);
+ }
+ return sortOrders.toArray(new SortOrder[0]);
+ } catch (IllegalArgumentException e) {
+ throw new TrinoException(
+ GRAVITINO_EXPRESSION_ERROR, "Error parsing the sort order field: " +
e.getMessage(), e);
+ }
+ }
+
+ private static void parseTransform(List<Transform> transforms, String value)
{
+ boolean match =
+ false
+ || tryMatch(
+ value,
+ IDENTIFILER_PATTERN,
+ (m) -> {
+ transforms.add(Transforms.identity(m.group(0)));
+ })
+ || tryMatch(
+ value,
+ YEAR_FUNCTION_PATTERN,
+ (m) -> {
+ transforms.add(Transforms.year(m.group(1)));
+ })
+ || tryMatch(
+ value,
+ MONTH_FUNCTION_PATTERN,
+ (m) -> {
+ transforms.add(Transforms.month(m.group(1)));
+ })
+ || tryMatch(
+ value,
+ DAY_FUNCTION_PATTERN,
+ (m) -> {
+ transforms.add(Transforms.day(m.group(1)));
+ })
+ || tryMatch(
+ value,
+ HOUR_FUNCTION_PATTERN,
+ (m) -> {
+ transforms.add(Transforms.hour(m.group(1)));
+ })
+ || tryMatch(
+ value,
+ BUCKET_FUNCTION_PATTERN,
+ (m) -> {
+ transforms.add(
+ Transforms.bucket(Integer.parseInt(m.group(2)), new
String[] {m.group(1)}));
+ })
+ || tryMatch(
+ value,
+ TRUNCATE_FUNCTION_PATTERN,
+ (m) -> {
+ transforms.add(
+ Transforms.truncate(Integer.parseInt(m.group(2)), new
String[] {m.group(1)}));
+ });
+ if (!match) {
+ throw new IllegalArgumentException("Unparsed expression: " + value);
+ }
+ }
+
+ private static boolean tryMatch(String value, Pattern pattern, MatchHandler
handler) {
+ Matcher matcher = pattern.matcher(value);
+ if (matcher.matches()) {
+ handler.invoke(matcher.toMatchResult());
+ return true;
+ }
+ return false;
+ }
+
+ private static String transFormToString(Transform transform) {
+ if (transform instanceof Transforms.IdentityTransform) {
+ return ((Transforms.IdentityTransform) transform).fieldName()[0];
+
+ } else if (transform instanceof Transforms.YearTransform
+ || transform instanceof Transforms.MonthTransform
+ || transform instanceof Transforms.DayTransform
+ || transform instanceof Transforms.HourTransform) {
+ return String.format(
+ "%s(%s)", transform.name(),
expressionToString(transform.arguments()[0]));
+
+ } else if (transform instanceof Transforms.BucketTransform) {
+ Transforms.BucketTransform bucketTransform =
(Transforms.BucketTransform) transform;
+ return String.format(
+ "%s(%s, %s)",
+ bucketTransform.name(), bucketTransform.fieldNames()[0][0],
bucketTransform.numBuckets());
+
+ } else if (transform instanceof Transforms.TruncateTransform) {
+ Transforms.TruncateTransform truncateTransform =
(Transforms.TruncateTransform) transform;
+ return String.format(
+ "%s(%s, %s)",
+ truncateTransform.name(), truncateTransform.fieldName()[0],
truncateTransform.width());
+ }
+
+ throw new IllegalArgumentException(
+ String.format(
+ "Unsupported transform %s with %d parameters: ",
+ transform, transform.arguments().length));
+ }
+
+ private static String expressionToString(Expression expression) {
+ if (expression instanceof NamedReference) {
+ return ((NamedReference) expression).fieldName()[0];
+ } else if (expression instanceof Literal<?>) {
+ return ((Literal<?>) expression).value().toString();
+ }
+ throw new IllegalArgumentException("Unsupported expression: " +
expression);
+ }
+
+ private static String sortOrderToString(SortOrder order) {
+ Expression orderExpression = order.expression();
+ if (!(orderExpression instanceof NamedReference)) {
+ throw new IllegalArgumentException(
+ "Only supported sort expression of NamedReference, the expression: "
+ orderExpression);
+ }
+
+ String columnName = ((NamedReference) orderExpression).fieldName()[0];
+ if (order.direction() == SortDirection.ASCENDING) {
+ if (order.nullOrdering() == NullOrdering.NULLS_LAST) {
+ return String.format("%s ASC NULLS LAST", columnName);
+ } else {
+ return columnName;
+ }
+ } else if (order.direction() == SortDirection.DESCENDING) {
+ if (order.nullOrdering() == NullOrdering.NULLS_FIRST) {
+ return String.format("%s DESC NULLS FIRST", columnName);
+ } else {
+ return columnName + " DESC";
+ }
+ }
+ throw new IllegalArgumentException("Unsupported sort order: " + order);
+ }
+
+ private static void parseSortOrder(List<SortOrder> sortOrders, String value)
{
+ boolean match =
+ false
+ || tryMatch(
+ value,
+ SROT_ORDER_PATTERN,
+ (m) -> {
+ NamedReference.FieldReference sortField =
NamedReference.field(m.group(0));
+ sortOrders.add(SortOrders.ascending(sortField));
+ })
+ || tryMatch(
+ value,
+ SORT_ORDER_WITH_SORT_DIRECTION_PATTERN,
+ (m) -> {
+ NamedReference.FieldReference sortField =
NamedReference.field(m.group(1));
+ SortDirection sortDirection =
+ m.group(1).toUpperCase().equals(SORT_DIRECTION_ASC)
+ ? SortDirection.ASCENDING
+ : SortDirection.DESCENDING;
+ NullOrdering nullOrdering =
+ sortDirection.equals(SortDirection.ASCENDING)
+ ? NullOrdering.NULLS_FIRST
+ : NullOrdering.NULLS_LAST;
+ sortOrders.add(SortOrders.of(sortField, sortDirection,
nullOrdering));
+ })
+ || tryMatch(
+ value,
+ SORT_ORDER_WITH_SORT_DIRECTION_AND_NULL_ORDERING_PATTERN,
+ (m) -> {
+ NamedReference.FieldReference sortField =
NamedReference.field(m.group(1));
+ SortDirection sortDirection =
+ m.group(2).toUpperCase().equals(SORT_DIRECTION_ASC)
+ ? SortDirection.ASCENDING
+ : SortDirection.DESCENDING;
+ NullOrdering nullOrdering =
+ m.group(3).toUpperCase().equals(NULL_ORDERING_FIRST)
+ ? NullOrdering.NULLS_FIRST
+ : NullOrdering.NULLS_LAST;
+ sortOrders.add(SortOrders.of(sortField, sortDirection,
nullOrdering));
+ });
+ if (!match) {
+ throw new IllegalArgumentException("Unparsed expression: " + value);
+ }
+ }
+
+ interface MatchHandler {
+ void invoke(MatchResult matchResult);
+ }
+}
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/iceberg/IcebergMetadataAdapter.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/iceberg/IcebergMetadataAdapter.java
index 321bc3bff..27ab016d0 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/iceberg/IcebergMetadataAdapter.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/iceberg/IcebergMetadataAdapter.java
@@ -24,21 +24,15 @@ import io.trino.spi.connector.ConnectorTableMetadata;
import io.trino.spi.connector.SchemaTableName;
import io.trino.spi.session.PropertyMetadata;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.gravitino.catalog.property.PropertyConverter;
-import org.apache.gravitino.rel.expressions.Expression;
-import org.apache.gravitino.rel.expressions.NamedReference;
import org.apache.gravitino.rel.expressions.sorts.SortOrder;
-import org.apache.gravitino.rel.expressions.sorts.SortOrders;
import org.apache.gravitino.rel.expressions.transforms.Transform;
-import org.apache.gravitino.rel.expressions.transforms.Transforms;
import
org.apache.gravitino.trino.connector.catalog.CatalogConnectorMetadataAdapter;
import org.apache.gravitino.trino.connector.metadata.GravitinoColumn;
import org.apache.gravitino.trino.connector.metadata.GravitinoTable;
@@ -126,20 +120,12 @@ public class IcebergMetadataAdapter extends
CatalogConnectorMetadataAdapter {
new GravitinoTable(schemaName, tableName, columns, comment,
properties);
if (!partitionColumns.isEmpty()) {
- Transform[] partitioning =
-
partitionColumns.stream().map(Transforms::identity).toArray(Transform[]::new);
+ Transform[] partitioning =
ExpressionUtil.partitionFiledToExpression(partitionColumns);
gravitinoTable.setPartitioning(partitioning);
}
if (!sortColumns.isEmpty()) {
- SortOrder[] sorting =
- sortColumns.stream()
- .map(
- sortingColumn -> {
- Expression expression =
NamedReference.field(sortingColumn);
- return SortOrders.ascending(expression);
- })
- .toArray(SortOrder[]::new);
+ SortOrder[] sorting =
ExpressionUtil.sortOrderFiledToExpression(sortColumns);
gravitinoTable.setSortOrders(sorting);
}
@@ -158,28 +144,15 @@ public class IcebergMetadataAdapter extends
CatalogConnectorMetadataAdapter {
Map<String, Object> properties =
toTrinoTableProperties(gravitinoTable.getProperties());
if (ArrayUtils.isNotEmpty(gravitinoTable.getPartitioning())) {
- // Only support simple partition now like partition by a, b, c.
- // Format like partition like partition by year(a), b, c is NOT
supported now.
properties.put(
IcebergPropertyMeta.ICEBERG_PARTITIONING_PROPERTY,
- gravitinoTable.getPartitioning().length > 0
- ? Arrays.stream(gravitinoTable.getPartitioning())
- .map(ts -> ((Transform.SingleFieldTransform)
ts).fieldName()[0])
- .collect(Collectors.toList())
- : Collections.emptyList());
+
ExpressionUtil.expressionToPartitionFiled(gravitinoTable.getPartitioning()));
}
if (ArrayUtils.isNotEmpty(gravitinoTable.getSortOrders())) {
- // Only support the simple format
properties.put(
IcebergPropertyMeta.ICEBERG_SORTED_BY_PROPERTY,
- Arrays.stream(gravitinoTable.getSortOrders())
- .map(
- sortOrder -> {
- Expression expression = sortOrder.expression();
- return ((NamedReference) expression).fieldName()[0];
- })
- .collect(Collectors.toList()));
+
ExpressionUtil.expressionToSortOrderFiled(gravitinoTable.getSortOrders()));
}
return new ConnectorTableMetadata(
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/iceberg/TestExpressionUtil.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/iceberg/TestExpressionUtil.java
new file mode 100644
index 000000000..086429f21
--- /dev/null
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/iceberg/TestExpressionUtil.java
@@ -0,0 +1,311 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.trino.connector.catalog.iceberg;
+
+import io.trino.spi.TrinoException;
+import java.util.List;
+import org.apache.gravitino.rel.expressions.NamedReference;
+import org.apache.gravitino.rel.expressions.sorts.NullOrdering;
+import org.apache.gravitino.rel.expressions.sorts.SortDirection;
+import org.apache.gravitino.rel.expressions.sorts.SortOrder;
+import org.apache.gravitino.rel.expressions.sorts.SortOrders;
+import org.apache.gravitino.rel.expressions.transforms.Transform;
+import org.apache.gravitino.rel.expressions.transforms.Transforms;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestExpressionUtil {
+
+ @Test
+ void testPartitionFiledToExpression() {
+ List<String> partitionField = List.of("f1");
+ Transform[] transforms =
ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(Transforms.identity(new String[] {"f1"}),
transforms[0]);
+
+ partitionField = List.of("year(f1)");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(Transforms.year("f1"), transforms[0]);
+
+ partitionField = List.of("MONTH(f2)");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(Transforms.month("f2"), transforms[0]);
+
+ partitionField = List.of("day(f3)");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(Transforms.day("f3"), transforms[0]);
+
+ partitionField = List.of("hour(f4)");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(transforms[0], Transforms.day("f4"));
+
+ partitionField = List.of("bucket(f2,10)");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(transforms[0], Transforms.bucket(10, new String[]
{"f2"}));
+
+ partitionField = List.of("TRUNCATE(f1, 3)");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(transforms[0], Transforms.truncate(3, new String[]
{"f1"}));
+
+ partitionField = List.of("truncate(f1, 3)");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(transforms[0], Transforms.truncate(3, new String[]
{"f1"}));
+
+ partitionField = List.of("month(order_date)", "BUCKET(account_number,
10)", "country");
+ transforms = ExpressionUtil.partitionFiledToExpression(partitionField);
+ Assertions.assertEquals(3, transforms.length);
+ Assertions.assertEquals(transforms[0], Transforms.month("order_date"));
+ Assertions.assertEquals(transforms[1], Transforms.bucket(10, new String[]
{"account_number"}));
+ Assertions.assertEquals(transforms[2], Transforms.identity(new String[]
{"country"}));
+ }
+
+ @Test
+ void testErrorOfPartitionFiledToExpression() {
+ // test invalid partition field name
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> partitionField = List.of("12");
+ ExpressionUtil.partitionFiledToExpression(partitionField);
+ },
+ "Error parsing partition field");
+
+ // test no exists partition function name
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> partitionField = List.of("abs(f1)");
+ ExpressionUtil.partitionFiledToExpression(partitionField);
+ },
+ "Error parsing partition field");
+
+ // test error function arguments
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> partitionField = List.of("year(f1, f2)");
+ ExpressionUtil.partitionFiledToExpression(partitionField);
+ },
+ "Error parsing partition field");
+
+ // test error function arguments
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> partitionField = List.of("year(12)");
+ ExpressionUtil.partitionFiledToExpression(partitionField);
+ },
+ "Error parsing partition field");
+
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> partitionField = List.of("buket(f1, f2)");
+ ExpressionUtil.partitionFiledToExpression(partitionField);
+ },
+ "Error parsing partition field");
+ }
+
+ @Test
+ void testExpressionToPartitionFiled() {
+ Transform[] transforms = new Transform[] {Transforms.identity(new String[]
{"f1"})};
+ List<String> partitionFiled =
ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "f1");
+
+ transforms = new Transform[] {Transforms.year("f1")};
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "year(f1)");
+
+ transforms = new Transform[] {Transforms.month("f2")};
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "month(f2)");
+
+ transforms = new Transform[] {Transforms.day("f3")};
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "day(f3)");
+
+ transforms = new Transform[] {Transforms.hour("f4")};
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "hour(f4)");
+
+ transforms = new Transform[] {Transforms.bucket(10, new String[] {"f2"})};
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "bucket(f2, 10)");
+
+ transforms = new Transform[] {Transforms.truncate(3, new String[] {"f1"})};
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "truncate(f1, 3)");
+
+ transforms = new Transform[] {Transforms.truncate(3, new String[] {"f1"})};
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(1, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "truncate(f1, 3)");
+
+ transforms =
+ new Transform[] {
+ Transforms.month("order_date"),
+ Transforms.bucket(10, new String[] {"account_number"}),
+ Transforms.identity(new String[] {"country"})
+ };
+ partitionFiled = ExpressionUtil.expressionToPartitionFiled(transforms);
+ Assertions.assertEquals(3, transforms.length);
+ Assertions.assertEquals(partitionFiled.get(0), "month(order_date)");
+ Assertions.assertEquals(partitionFiled.get(1), "bucket(account_number,
10)");
+ Assertions.assertEquals(partitionFiled.get(2), "country");
+ }
+
+ @Test
+ void testExpressionToSortOrderFiled() {
+ SortOrder[] sortOrders = new SortOrder[]
{SortOrders.ascending(NamedReference.field("f1"))};
+ List<String> sortOrderFiled =
ExpressionUtil.expressionToSortOrderFiled(sortOrders);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals("f1", sortOrderFiled.get(0));
+
+ sortOrders = new SortOrder[]
{SortOrders.descending(NamedReference.field("f2"))};
+ sortOrderFiled = ExpressionUtil.expressionToSortOrderFiled(sortOrders);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals("f2 DESC", sortOrderFiled.get(0));
+
+ sortOrders =
+ new SortOrder[] {
+ SortOrders.of(
+ NamedReference.field("f1"), SortDirection.ASCENDING,
NullOrdering.NULLS_LAST)
+ };
+ sortOrderFiled = ExpressionUtil.expressionToSortOrderFiled(sortOrders);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals("f1 ASC NULLS LAST", sortOrderFiled.get(0));
+
+ sortOrders =
+ new SortOrder[] {
+ SortOrders.of(
+ NamedReference.field("f2"), SortDirection.DESCENDING,
NullOrdering.NULLS_FIRST)
+ };
+ sortOrderFiled = ExpressionUtil.expressionToSortOrderFiled(sortOrders);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals("f2 DESC NULLS FIRST", sortOrderFiled.get(0));
+
+ sortOrders =
+ new SortOrder[] {
+ SortOrders.ascending(NamedReference.field("f1")),
+ SortOrders.descending(NamedReference.field("f2")),
+ SortOrders.of(
+ NamedReference.field("f3"), SortDirection.ASCENDING,
NullOrdering.NULLS_LAST),
+ SortOrders.of(
+ NamedReference.field("f4"), SortDirection.DESCENDING,
NullOrdering.NULLS_FIRST)
+ };
+ sortOrderFiled = ExpressionUtil.expressionToSortOrderFiled(sortOrders);
+ Assertions.assertEquals(4, sortOrders.length);
+ Assertions.assertEquals("f1", sortOrderFiled.get(0));
+ Assertions.assertEquals("f2 DESC", sortOrderFiled.get(1));
+ Assertions.assertEquals("f3 ASC NULLS LAST", sortOrderFiled.get(2));
+ Assertions.assertEquals("f4 DESC NULLS FIRST", sortOrderFiled.get(3));
+ }
+
+ @Test
+ void testSortOrderFiledToExpression() {
+ List<String> sortOrderFiled = List.of("f1");
+ SortOrder[] sortOrders =
ExpressionUtil.sortOrderFiledToExpression(sortOrderFiled);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals(SortOrders.ascending(NamedReference.field("f1")),
sortOrders[0]);
+
+ sortOrderFiled = List.of("F2 desc");
+ sortOrders = ExpressionUtil.sortOrderFiledToExpression(sortOrderFiled);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals(SortOrders.descending(NamedReference.field("F2")),
sortOrders[0]);
+
+ sortOrderFiled = List.of("f1 ASC NULLS LAST");
+ sortOrders = ExpressionUtil.sortOrderFiledToExpression(sortOrderFiled);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals(
+ SortOrders.of(NamedReference.field("f1"), SortDirection.ASCENDING,
NullOrdering.NULLS_LAST),
+ sortOrders[0]);
+
+ sortOrderFiled = List.of("f2 desc nulls first");
+ sortOrders = ExpressionUtil.sortOrderFiledToExpression(sortOrderFiled);
+ Assertions.assertEquals(1, sortOrders.length);
+ Assertions.assertEquals(
+ SortOrders.of(
+ NamedReference.field("f2"), SortDirection.DESCENDING,
NullOrdering.NULLS_FIRST),
+ sortOrders[0]);
+
+ sortOrderFiled = List.of("f1", "f2 DESC", "f3 ASC NULLS LAST", "F4 DESC
NULLS FIRST");
+ sortOrders = ExpressionUtil.sortOrderFiledToExpression(sortOrderFiled);
+ Assertions.assertEquals(4, sortOrders.length);
+ Assertions.assertEquals(SortOrders.ascending(NamedReference.field("f1")),
sortOrders[0]);
+ Assertions.assertEquals(SortOrders.descending(NamedReference.field("f2")),
sortOrders[1]);
+ Assertions.assertEquals(
+ SortOrders.of(NamedReference.field("f3"), SortDirection.ASCENDING,
NullOrdering.NULLS_LAST),
+ sortOrders[2]);
+ Assertions.assertEquals(
+ SortOrders.of(
+ NamedReference.field("F4"), SortDirection.DESCENDING,
NullOrdering.NULLS_FIRST),
+ sortOrders[3]);
+ }
+
+ @Test
+ void testErrorOfSortOrderFiledToExpression() {
+ // test invalid sort order field name
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> sortOrderFields = List.of("12");
+ ExpressionUtil.partitionFiledToExpression(sortOrderFields);
+ },
+ "Error parsing partition field");
+
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> sortOrderFields = List.of("f12", "1");
+ ExpressionUtil.partitionFiledToExpression(sortOrderFields);
+ },
+ "Error parsing partition field");
+
+ // test invalid sort order format
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> sortOrderFields = List.of("f12 dxxx");
+ ExpressionUtil.partitionFiledToExpression(sortOrderFields);
+ },
+ "Error parsing partition field");
+
+ Assertions.assertThrows(
+ TrinoException.class,
+ () -> {
+ List<String> sortOrderFields = List.of("f12 asc nulls all");
+ ExpressionUtil.partitionFiledToExpression(sortOrderFields);
+ },
+ "Error parsing partition field");
+ }
+}