[ https://issues.apache.org/jira/browse/CALCITE-7062?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17987319#comment-17987319 ]
Vladimir Steshin edited comment on CALCITE-7062 at 7/2/25 4:50 AM: ------------------------------------------------------------------- I'm not sure this relates to a direct casting. It looks like a row type coercion. UNION may combine a nullable and non-nullable from different sources. The result type is nullable in this case. Probably, considering of "targetType.isNullable" may depend on current operation. was (Author: vladsz83): I'm not sure this relates to a direct casting. It looks like a row type coercion. UNION may combine a nullable and non-nullable from different sources. The result looks like nullable in this case. Probably, considering of "targetType.isNullable" may depend on current operation. > Row type of UNION may ignore a column's nullability > --------------------------------------------------- > > Key: CALCITE-7062 > URL: https://issues.apache.org/jira/browse/CALCITE-7062 > Project: Calcite > Issue Type: Bug > Affects Versions: 1.39.0, 1.40.0 > Reporter: Vladimir Steshin > Priority: Minor > > I'm not sure this is a problem. Is that ok? I found it by one of type casting > tests. > Consider: > {code:java} > package org.apache.calcite.test; > class ServerTest { > @Test void testNullableCoercionInUnion() throws Exception { > try (Connection c = connect(); Statement s = c.createStatement()) { > s.execute("create table t1 (i smallint not null)"); > s.execute("create table t2 (i bigint)"); > s.executeUpdate("insert into t1 values (1)"); > s.executeUpdate("insert into t2 values (10), (null)"); > try (ResultSet r = s.executeQuery("select i from t1 union all select i > from t2")) { > assertTrue(r.next()); > assertThat(r.getLong("i"), is(1L)); > assertTrue(r.next()); > assertThat(r.getLong("i"), is(10L)); > assertTrue(r.next()); > // The result has a null value. It is ok. > assertNull(r.getObject("i")); > } > // The plan. > try (ResultSet r = s.executeQuery("explain plan for select i from t1 > union all select i from t2")) { > assertTrue(r.next()); > String plan = r.getString(1); > // Fails here. It actually casts to a BIGINT NOT NULL whereas the > actual resuls contain a NULL. > assertTrue(plan.contains("[CAST($t0):BIGINT]")); > } > } > } > // Or > @Test void test2() throws Exception { > try (Connection c = connect(); Statement s = c.createStatement()) { > s.execute("create table t1(id int not null, test TIMESTAMP NOT NULL)"); > s.execute("create table t2(id int not null, test VARCHAR)"); > s.execute("insert into t1 values (1, SYSTIMESTAMP)"); > s.execute("insert into t2 values (1, CAST(SYSDATE AS VARCHAR))"); > String query = "SELECT cast(test as date) as test FROM t2 " + > "UNION ALL " + > "select test from t1"; > // Fails here. Similarly in v1.37 / v.38 > s.execute(query); > } > }{code} > The least restrictive type is _nullable BIGINT._ Looks ok. However, > _StandardConvertletTable#convertCast(...)_ produces a _CAST_ to a {_}NOT > NULLABLE{_}. > Regarding my research, the nullability is lost somewhere around > {code:java} > class AbstractTypeCoercion > RelDataType syncAttributes( > RelDataType fromType, > RelDataType toType) { > RelDataType syncedType = toType; > if (fromType != null) { > syncedType = factory.createTypeWithNullability(syncedType, > fromType.isNullable()); > ... > }{code} > It doesn't take in account {_}toType.isNullable(){_}. > And also in > {code:java} > class SqlCastFunction > private static RelDataType > createTypeWithNullabilityFromExpr(RelDataTypeFactory typeFactory, > RelDataType expressionType, RelDataType targetType, boolean safe) { > boolean isNullable = expressionType.isNullable() || safe; > ... > } {code} > > The same: _targetType.isNullable()_ is ignored. > > Te *second test* produces: > {code:java} > ava.lang.AssertionError: Conversion to relational algebra failed to preserve > datatypes: > validated type: > RecordType(TIMESTAMP(0) NOT NULL TEST) NOT NULL > converted type: > RecordType(TIMESTAMP(0) TEST) NOT NULL > rel: > LogicalUnion(all=[true]) > LogicalProject(TEST=[CAST(CAST($1):DATE):TIMESTAMP(0)]) > LogicalTableScan(table=[[T2]]) > LogicalProject(TEST=[$1]) > LogicalTableScan(table=[[T1]]){code} > {code:java} > at > org.apache.calcite.sql2rel.SqlToRelConverter.checkConvertedType(SqlToRelConverter.java:524) > at > org.apache.calcite.sql2rel.SqlToRelConverter.convertQuery(SqlToRelConverter.java:643) > at org.apache.calcite.prepare.Prepare.prepareSql(Prepare.java:258) > at org.apache.calcite.prepare.Prepare.prepareSql(Prepare.java:221) at > org.apache.calcite.prepare.CalcitePrepareImpl.prepare2_(CalcitePrepareImpl.java:673) > at > org.apache.calcite.prepare.CalcitePrepareImpl.prepare_(CalcitePrepareImpl.java:524) > at > org.apache.calcite.prepare.CalcitePrepareImpl.prepareSql(CalcitePrepareImpl.java:492) > at > org.apache.calcite.jdbc.CalciteConnectionImpl.parseQuery(CalciteConnectionImpl.java:246) > at > org.apache.calcite.jdbc.CalciteMetaImpl.prepareAndExecute(CalciteMetaImpl.java:654){code} -- This message was sent by Atlassian Jira (v8.20.10#820010)