Skip to content

Commit a2d647d

Browse files
authored
Create SqlCallTransformer for generic transformations on SqlCall (#336)
1 parent fda6891 commit a2d647d

File tree

9 files changed

+239
-197
lines changed

9 files changed

+239
-197
lines changed

coral-common/src/main/java/com/linkedin/coral/common/ToRelConverter.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2017-2022 LinkedIn Corporation. All rights reserved.
2+
* Copyright 2017-2023 LinkedIn Corporation. All rights reserved.
33
* Licensed under the BSD-2 Clause license.
44
* See LICENSE in the project root for license information.
55
*/
@@ -65,14 +65,6 @@ public abstract class ToRelConverter {
6565

6666
protected abstract SqlNode toSqlNode(String sql, org.apache.hadoop.hive.metastore.api.Table hiveView);
6767

68-
/**
69-
* Apply series of transforms to convert Hive relnode to
70-
* standardized intermediate representation.
71-
* @param relNode calcite relnode representing hive query
72-
* @return standard representation of input query as relnode
73-
*/
74-
protected abstract RelNode standardizeRel(RelNode relNode);
75-
7668
protected ToRelConverter(@Nonnull HiveMetastoreClient hiveMetastoreClient) {
7769
checkNotNull(hiveMetastoreClient);
7870
this.hiveMetastoreClient = hiveMetastoreClient;
@@ -129,7 +121,6 @@ public RelNode convertSql(String sql) {
129121
*/
130122
public RelNode convertView(String hiveDbName, String hiveViewName) {
131123
SqlNode sqlNode = processView(hiveDbName, hiveViewName);
132-
sqlNode.accept(new FuzzyUnionSqlRewriter(hiveViewName, this));
133124
return toRel(sqlNode);
134125
}
135126

@@ -164,7 +155,7 @@ public SqlNode processView(String dbName, String tableName) {
164155
@VisibleForTesting
165156
protected RelNode toRel(SqlNode sqlNode) {
166157
RelRoot root = getSqlToRelConverter().convertQuery(sqlNode, true, true);
167-
return standardizeRel(root.rel);
158+
return root.rel;
168159
}
169160

170161
/**
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Copyright 2017-2023 LinkedIn Corporation. All rights reserved.
3+
* Licensed under the BSD-2 Clause license.
4+
* See LICENSE in the project root for license information.
5+
*/
6+
package com.linkedin.coral.common.transformers;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
import org.apache.calcite.rel.type.RelDataType;
12+
import org.apache.calcite.sql.SqlCall;
13+
import org.apache.calcite.sql.SqlNode;
14+
import org.apache.calcite.sql.SqlNodeList;
15+
import org.apache.calcite.sql.SqlSelect;
16+
import org.apache.calcite.sql.validate.SqlValidator;
17+
18+
19+
/**
20+
* Abstract class for generic transformations on SqlCalls
21+
*/
22+
public abstract class SqlCallTransformer {
23+
private SqlValidator sqlValidator;
24+
private final List<SqlSelect> topSelectNodes = new ArrayList<>();
25+
26+
public SqlCallTransformer() {
27+
28+
}
29+
30+
public SqlCallTransformer(SqlValidator sqlValidator) {
31+
this.sqlValidator = sqlValidator;
32+
}
33+
34+
/**
35+
* Predicate of the transformer, it’s used to determine if the SqlCall should be transformed or not
36+
*/
37+
protected abstract boolean predicate(SqlCall sqlCall);
38+
39+
/**
40+
* Implementation of the transformation, returns the transformed SqlCall
41+
*/
42+
protected abstract SqlCall transform(SqlCall sqlCall);
43+
44+
/**
45+
* Public entry of the transformer, it returns the result of transformed SqlCall if `predicate(SqlCall)` returns true,
46+
* otherwise returns the input SqlCall without any transformation
47+
*/
48+
public SqlCall apply(SqlCall sqlCall) {
49+
if (sqlCall instanceof SqlSelect) {
50+
this.topSelectNodes.add((SqlSelect) sqlCall);
51+
}
52+
if (predicate(sqlCall)) {
53+
return transform(sqlCall);
54+
} else {
55+
return sqlCall;
56+
}
57+
}
58+
59+
/**
60+
* To get the RelDatatype of a SqlNode, we iterate through `topSelectNodes` from the latest visited to the oldest visited,
61+
* for each `topSelectNode`, we create a minimum dummy SqlSelect: SELECT `sqlNode` FROM `topSelectNode.getFrom()`
62+
* If the SqlValidator is able to validate the dummy SqlSelect, return the SqlNode's RelDataType directly.
63+
*
64+
* We can't just use the latest visited `topSelectNode` to construct the dummy SqlSelect because of
65+
* the following corner case:
66+
*
67+
* CREATE db.tbl(col1 array(int));
68+
*
69+
* SELECT * FROM (
70+
* SELECT col1 FROM db.tbl
71+
* ) LATERAL JOIN EXPLODE(col1) t AS a WHERE t.a = 0
72+
*
73+
* If we want to derive the datatype of `t.a`, the latest visited `topSelectNode` will be `SELECT col1 FROM db.tbl`,
74+
* however, `t.a` doesn't exist in `db.tbl`, so it would throw exception.
75+
* Therefore, we need to store all the `topSelectNodes` (both inner `SELECT col1 FROM db.tbl` and the whole SQL)
76+
* in the `topSelectNodes` list and traverse them from the latest visited to the oldest visited, return the datatype
77+
* directly once it can be derived without exception.
78+
*
79+
* Note: This implementation assumes that the parent SqlSelect is visited before determining the datatype of the child
80+
* SqlNode, which is typically achieved by traversing the SqlNode tree using SqlShuttle.
81+
* The implementation might be updated to not rely on this assumption for determining the datatype of the child SqlNode.
82+
*/
83+
protected RelDataType getRelDataType(SqlNode sqlNode) {
84+
if (sqlValidator == null) {
85+
throw new RuntimeException("SqlValidator does not exist to derive the RelDataType for SqlNode " + sqlNode);
86+
}
87+
for (int i = topSelectNodes.size() - 1; i >= 0; --i) {
88+
final SqlSelect topSelectNode = topSelectNodes.get(i);
89+
final SqlSelect dummySqlSelect = new SqlSelect(topSelectNode.getParserPosition(), null, SqlNodeList.of(sqlNode),
90+
topSelectNode.getFrom(), null, null, null, null, null, null, null);
91+
try {
92+
sqlValidator.validate(dummySqlSelect);
93+
return sqlValidator.getValidatedNodeType(dummySqlSelect).getFieldList().get(0).getType();
94+
} catch (Throwable ignored) {
95+
}
96+
}
97+
throw new RuntimeException("Failed to derive the RelDataType for SqlNode " + sqlNode);
98+
}
99+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright 2023 LinkedIn Corporation. All rights reserved.
3+
* Licensed under the BSD-2 Clause license.
4+
* See LICENSE in the project root for license information.
5+
*/
6+
package com.linkedin.coral.common.transformers;
7+
8+
import java.util.Arrays;
9+
10+
import com.google.common.collect.ImmutableList;
11+
12+
import org.apache.calcite.sql.SqlCall;
13+
14+
15+
/**
16+
* Container for SqlCallTransformer
17+
*/
18+
public class SqlCallTransformers {
19+
private final ImmutableList<SqlCallTransformer> sqlCallTransformers;
20+
21+
SqlCallTransformers(ImmutableList<SqlCallTransformer> sqlCallTransformers) {
22+
this.sqlCallTransformers = sqlCallTransformers;
23+
}
24+
25+
public static SqlCallTransformers of(SqlCallTransformer... sqlCallTransformers) {
26+
return new SqlCallTransformers(
27+
ImmutableList.<SqlCallTransformer> builder().addAll(Arrays.asList(sqlCallTransformers)).build());
28+
}
29+
30+
public static SqlCallTransformers of(ImmutableList<SqlCallTransformer> sqlCallTransformers) {
31+
return new SqlCallTransformers(sqlCallTransformers);
32+
}
33+
34+
public SqlCall apply(SqlCall sqlCall) {
35+
for (SqlCallTransformer sqlCallTransformer : sqlCallTransformers) {
36+
sqlCall = sqlCallTransformer.apply(sqlCall);
37+
}
38+
return sqlCall;
39+
}
40+
}

coral-hive/src/main/java/com/linkedin/coral/hive/hive2rel/HiveRelConverter.java

Lines changed: 0 additions & 170 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright 2017-2023 LinkedIn Corporation. All rights reserved.
3+
* Licensed under the BSD-2 Clause license.
4+
* See LICENSE in the project root for license information.
5+
*/
6+
package com.linkedin.coral.hive.hive2rel;
7+
8+
import org.apache.calcite.sql.SqlCall;
9+
import org.apache.calcite.sql.SqlNode;
10+
import org.apache.calcite.sql.util.SqlShuttle;
11+
import org.apache.calcite.sql.validate.SqlValidator;
12+
13+
import com.linkedin.coral.common.transformers.SqlCallTransformers;
14+
import com.linkedin.coral.transformers.ShiftArrayIndexTransformer;
15+
16+
17+
/**
18+
* Converts Hive SqlNode to Coral SqlNode
19+
*/
20+
public class HiveSqlNodeToCoralSqlNodeConverter extends SqlShuttle {
21+
private final SqlCallTransformers operatorTransformerList;
22+
23+
public HiveSqlNodeToCoralSqlNodeConverter(SqlValidator sqlValidator) {
24+
operatorTransformerList = SqlCallTransformers.of(new ShiftArrayIndexTransformer(sqlValidator));
25+
}
26+
27+
@Override
28+
public SqlNode visit(SqlCall call) {
29+
return super.visit(operatorTransformerList.apply(call));
30+
}
31+
}

0 commit comments

Comments
 (0)