Skip to content

Commit 3e9a6a1

Browse files
committed
Tests with deeper nested ROW structures
Signed-off-by: Mihai Budiu <[email protected]>
1 parent 0781344 commit 3e9a6a1

File tree

7 files changed

+99
-13
lines changed

7 files changed

+99
-13
lines changed

core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ RelDataType createMultisetType(
179179
* Creates a type that is the same as another type but with possibly
180180
* different nullability. The output type may be identical to the input
181181
* type. For type systems without a concept of nullability, the return value
182-
* is always the same as the input.
182+
* is always the same as the input. This function never returns a nullable
183+
* struct type; when applied to a struct type, it recursively makes all fields
184+
* nullable and the struct itself is non-nullable.
183185
*
184186
* @param type input type
185187
* @param nullable true to request a nullable type; false to request a NOT
@@ -191,6 +193,24 @@ RelDataType createTypeWithNullability(
191193
RelDataType type,
192194
boolean nullable);
193195

196+
/**
197+
* Creates a type that is the same as another type but with possibly
198+
* different nullability. The output type may be identical to the input
199+
* type. For type systems without a concept of nullability, the return value
200+
* is always the same as the input. This differs from {@link
201+
* #createTypeWithNullability(RelDataType, boolean)} in the handling of struct
202+
* types. This function returns a nullable struct.
203+
*
204+
* @param type input type
205+
* @param nullable true to request a nullable type; false to request a NOT
206+
* NULL type
207+
* @return output type, same as input type except with specified nullability
208+
* @throws NullPointerException if type is null
209+
*/
210+
RelDataType enforceTypeWithNullability(
211+
RelDataType type,
212+
boolean nullable);
213+
194214
/**
195215
* Creates a type that is the same as another type but with possibly
196216
* different charset or collation. For types without a concept of charset or

core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java

+25
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,31 @@ private RelDataType copyRecordType(
410410
return canonize(newType);
411411
}
412412

413+
@Override public RelDataType enforceTypeWithNullability(
414+
final RelDataType type,
415+
final boolean nullable) {
416+
requireNonNull(type, "type");
417+
RelDataType newType;
418+
if (type.isNullable() == nullable) {
419+
newType = type;
420+
} else if (type instanceof RelRecordType) {
421+
return createStructType(type.getStructKind(),
422+
new AbstractList<RelDataType>() {
423+
@Override public RelDataType get(int index) {
424+
return type.getFieldList().get(index).getType();
425+
}
426+
427+
@Override public int size() {
428+
return type.getFieldCount();
429+
}
430+
},
431+
type.getFieldNames(), nullable);
432+
} else {
433+
newType = copySimpleType(type, nullable);
434+
}
435+
return canonize(newType);
436+
}
437+
413438
/**
414439
* Registers a type, or returns the existing type if it is already
415440
* registered.

core/src/main/java/org/apache/calcite/rex/RexBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ private RexNode makeFieldAccessInternal(
257257
}
258258

259259
if (expr.getType().isNullable()) {
260-
fieldType = typeFactory.createTypeWithNullability(fieldType, true);
260+
fieldType = typeFactory.enforceTypeWithNullability(fieldType, true);
261261
}
262262
return new RexFieldAccess(expr, field, fieldType);
263263
}

core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ protected static void checkPrecScale(SqlTypeName typeName,
126126
/**
127127
* Constructs a type with nullablity.
128128
*/
129-
BasicSqlType createWithNullability(boolean nullable) {
129+
public BasicSqlType createWithNullability(boolean nullable) {
130130
if (nullable == this.isNullable) {
131131
return this;
132132
}

core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java

+22
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,28 @@ public SqlTypeFactoryImpl(RelDataTypeSystem typeSystem) {
253253
return canonize(newType);
254254
}
255255

256+
@Override public RelDataType enforceTypeWithNullability(
257+
final RelDataType type,
258+
final boolean nullable) {
259+
final RelDataType newType;
260+
if (type instanceof BasicSqlType) {
261+
newType = ((BasicSqlType) type).createWithNullability(nullable);
262+
} else if (type instanceof MapSqlType) {
263+
newType = copyMapType(type, nullable);
264+
} else if (type instanceof ArraySqlType) {
265+
newType = copyArrayType(type, nullable);
266+
} else if (type instanceof MultisetSqlType) {
267+
newType = copyMultisetType(type, nullable);
268+
} else if (type instanceof IntervalSqlType) {
269+
newType = copyIntervalType(type, nullable);
270+
} else if (type instanceof ObjectSqlType) {
271+
newType = copyObjectType(type, nullable);
272+
} else {
273+
return super.enforceTypeWithNullability(type, nullable);
274+
}
275+
return canonize(newType);
276+
}
277+
256278
private static void assertBasic(SqlTypeName typeName) {
257279
assert typeName != null;
258280
assert typeName != SqlTypeName.MULTISET

core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -7173,7 +7173,7 @@ private class DeriveTypeVisitor implements SqlVisitor<RelDataType> {
71737173
// Consider CREATE TABLE T(p ROW(k VARCHAR) NULL);
71747174
// Since T.p is nullable, T.p.k also has to be nullable, even if
71757175
// the type of k itself is not nullable.
7176-
type = getTypeFactory().createTypeWithNullability(type, true);
7176+
type = getTypeFactory().enforceTypeWithNullability(type, true);
71777177
}
71787178
}
71797179
type =

core/src/test/java/org/apache/calcite/test/TableInRootSchemaTest.java

+28-9
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,18 @@ protected TableWithNullableRowInMap() {
129129
// Schema contains a column whose type is MAP<VARCHAR, ROW(VARCHAR)>, but
130130
// the ROW type can be nullable. This can conceivably be created by a
131131
// declaration such as
132-
// CREATE TABLE T(p MAP<VARCHAR, ROW(k VARCHAR)>);
132+
// CREATE TABLE T(P MAP<VARCHAR, ROW(K VARCHAR NON NULL, S VARCHAR NULL)>);
133133
final RelDataType colType =
134134
typeFactory.createMapType(typeFactory.createSqlType(SqlTypeName.VARCHAR),
135135
new RelRecordType(
136136
StructKind.PEEK_FIELDS,
137137
ImmutableList.of(
138138
new RelDataTypeFieldImpl("K", 0,
139-
typeFactory.createSqlType(SqlTypeName.VARCHAR))), true));
139+
typeFactory.createSqlType(SqlTypeName.VARCHAR)),
140+
new RelDataTypeFieldImpl("S", 1,
141+
typeFactory.createTypeWithNullability(
142+
typeFactory.createSqlType(SqlTypeName.VARCHAR), true))),
143+
true));
140144
columnDesc.add("P", colType);
141145
return typeFactory.createStructType(columnDesc);
142146
}
@@ -151,22 +155,37 @@ protected TableWithNullableRowToplevel() {
151155
@Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
152156
final PairList<String, RelDataType> columnDesc = PairList.withCapacity(1);
153157
// This table can conceivably be created by a declaration such as
154-
// CREATE TABLE T(p ROW(k VARCHAR) NULL);
155-
final RelDataType colType =
158+
// CREATE TABLE T(P ROW(K VARCHAR NOT NULL) NULL,
159+
// Q ROW(S ROW(L VARCHAR NOT NULL, M VARCHAR NULL) NULL) NULL);
160+
final RelDataType pColType =
156161
new RelRecordType(
157162
StructKind.PEEK_FIELDS, ImmutableList.of(
158163
new RelDataTypeFieldImpl(
159164
"K", 0, typeFactory.createSqlType(SqlTypeName.VARCHAR))),
160165
true);
161-
columnDesc.add("P", colType);
166+
final RelDataType sType =
167+
new RelRecordType(
168+
StructKind.PEEK_FIELDS, ImmutableList.of(
169+
new RelDataTypeFieldImpl(
170+
"L", 0, typeFactory.createSqlType(SqlTypeName.VARCHAR)),
171+
new RelDataTypeFieldImpl(
172+
"M", 1, typeFactory.createSqlType(SqlTypeName.VARCHAR))),
173+
false);
174+
final RelDataType qColType =
175+
new RelRecordType(
176+
StructKind.PEEK_FIELDS, ImmutableList.of(
177+
new RelDataTypeFieldImpl("S", 0, sType)),
178+
true);
179+
columnDesc.add("P", pColType);
180+
columnDesc.add("Q", qColType);
162181
return typeFactory.createStructType(columnDesc);
163182
}
164183
}
165184

166185
/** Test case for
167186
* <a href="https://issues.apache.org/jira/browse/CALCITE-6764">[CALCITE-6764]
168187
* Field access from a nullable ROW should be nullable</a>. */
169-
@Test void testNullableValue() throws Exception {
188+
@Test void testNullableRowInMap() throws Exception {
170189
Connection connection = DriverManager.getConnection("jdbc:calcite:");
171190
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
172191
calciteConnection.getRootSchema().add("T", new TableWithNullableRowInMap());
@@ -175,7 +194,7 @@ protected TableWithNullableRowToplevel() {
175194
// java.lang.RuntimeException: java.lang.AssertionError:
176195
// Conversion to relational algebra failed to preserve datatypes:
177196
// validated type:
178-
ResultSet resultSet = statement.executeQuery("SELECT P['a'].K FROM T");
197+
ResultSet resultSet = statement.executeQuery("SELECT P['a'].K, P['a'].S FROM T");
179198
resultSet.close();
180199
statement.close();
181200
connection.close();
@@ -184,7 +203,7 @@ protected TableWithNullableRowToplevel() {
184203
/** Test case for
185204
* <a href="https://issues.apache.org/jira/browse/CALCITE-6764">[CALCITE-6764]
186205
* Field access from a nullable ROW should be nullable</a>. */
187-
@Test void testNullableValue2() throws Exception {
206+
@Test void testNullableRowTopLevel() throws Exception {
188207
Connection connection = DriverManager.getConnection("jdbc:calcite:");
189208
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
190209
calciteConnection.getRootSchema().add("T", new TableWithNullableRowToplevel());
@@ -193,7 +212,7 @@ protected TableWithNullableRowToplevel() {
193212
// java.lang.RuntimeException: java.lang.AssertionError:
194213
// Conversion to relational algebra failed to preserve datatypes:
195214
// validated type:
196-
ResultSet resultSet = statement.executeQuery("SELECT t.p.k FROM T");
215+
ResultSet resultSet = statement.executeQuery("SELECT T.P.K, T.Q.S, T.Q.S.L, T.Q.S.M FROM T");
197216
resultSet.close();
198217
statement.close();
199218
connection.close();

0 commit comments

Comments
 (0)