Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,16 @@ public static RelDataType convert(StructTypeInfo structType, final RelDataTypeFa
}

// Mimic the StructTypeInfo conversion to convert a UnionTypeInfo to the corresponding RelDataType
// The schema of output Struct conforms to https://github.com/trinodb/trino/pull/3483
public static RelDataType convert(UnionTypeInfo unionType, RelDataTypeFactory dtFactory) {
List<RelDataType> fTypes = unionType.getAllUnionObjectTypeInfos().stream()
.map(typeInfo -> convert(typeInfo, dtFactory)).collect(Collectors.toList());
List<String> fNames = IntStream.range(0, unionType.getAllUnionObjectTypeInfos().size()).mapToObj(i -> "tag_" + i)
List<String> fNames = IntStream.range(0, unionType.getAllUnionObjectTypeInfos().size()).mapToObj(i -> "field" + i)
.collect(Collectors.toList());
if (fNames.size() > 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need this if statement since an empty union is a wrong schema, to begin with, maybe we could add a Preconditions.checkState to check it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently how do we prevent empty union to appear?
The reason for this branch is to preserve the semantic of the same code block earlier. If we add a precondition check, encountering empty union will result in unchecked exception but it will simply return an empty RelDataType before the change. is that what we want ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that here https://github.com/trinodb/trino/pull/3483/files# the size is not checked. Does the Avro or ORC standard say anything about this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ORC spec doesn't mention it while I do see uniontype<> happening in production. Regardless, this check is there to keep parity with the original (or we could end up with an additional tag field).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if using the current code, uniontype<> ends up in an empty struct? I recall there are some issues with empty struct somewhere, either in Iceberg or in Spark. So I'm not sure if this whole corner case will work or not.
My point is if we explicitly announce this special case's behavior is undefined, then users have the responsibility to create the correct schema, it's a tradeoff between us and the users, I'm willing to accept either way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our references should be: 1- what the standard says (again for Avro or ORC) 2- what the Trino transformation does. Hopefully all of them align. If not, we can discuss how to move forward.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another special case is union with only a single type uniontype<[type]>. We should consider whether we want to support it and make sure its semantics is consistent across Trino and Iceberg implementation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There aren't any spec that I could find regarding this point in either Avro or ORC, and looks like Trino doesn't check the size either.
Taking a step back, I also don't think Coral is in right position to gate such usage. So I am leaning towards let such case pass through (which means a check like fNames.size > 0 is necessary since we see struct<> as a legit type).

I am also OK to add a precondition check to fail the translation. But again, leaning towards the former option.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to the current approach

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline. The objective is to match the Trino schema. We should remove the check from here since it is not used there.

fTypes.add(0, dtFactory.createSqlType(SqlTypeName.TINYINT));
fNames.add(0, "tag");
}

RelDataType rowType = dtFactory.createStructType(fTypes, fNames);
return dtFactory.createTypeWithNullability(rowType, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
import com.linkedin.coral.common.HiveSchema;
import com.linkedin.coral.common.HiveTable;

import static org.testng.Assert.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;


public class HiveTableTest {
Expand Down Expand Up @@ -65,6 +67,23 @@ public void testTable() throws Exception {
assertEquals(colC.getType().getComponentType().getSqlTypeName(), SqlTypeName.DOUBLE);
}

@Test
public void testTableWithUnion() throws Exception {
final RelDataTypeFactory typeFactory = new JavaTypeFactoryImpl();

// test handling of union
Comment thread
autumnust marked this conversation as resolved.
Table unionTable = getTable("default", "union_table");
// union_table:(foo uniontype<int, double, array<string>, struct<a:int,b:string>>)
// expected outcome schema: struct<tag:tinyint, field0:int, field1:double, field2:array<string>, field3:struct<a:int,b:string>>
RelDataType rowType = unionTable.getRowType(typeFactory);
assertNotNull(rowType);

String expectedTypeString =
"RecordType(" + "RecordType(" + "TINYINT tag, INTEGER field0, DOUBLE field1, VARCHAR(65536) ARRAY field2, "
+ "RecordType(INTEGER a, VARCHAR(65536) b) field3" + ") " + "foo)";
assertEquals(rowType.toString(), expectedTypeString);
}

@Test
public void testGetDaliFunctionParams() throws HiveException, TException {
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ public void testSchemaPromotionView() {
assertEquals(CoralSpark.create(relNode).getSparkSql(), targetSql);
}

@Test
@Test(enabled = false)
public void testUnionExtractUDF() {
RelNode relNode = TestUtils.toRelNode("SELECT extract_union(foo) from union_table");
String targetSql = String.join("\n", "SELECT foo", "FROM default.union_table");
Expand Down