|
22 | 22 | import java.lang.reflect.Type; |
23 | 23 | import java.util.Date; |
24 | 24 | import java.util.Map; |
| 25 | +import java.util.concurrent.ConcurrentHashMap; |
25 | 26 | import java.util.stream.IntStream; |
26 | 27 | import org.apache.beam.sdk.schemas.Schema; |
27 | 28 | import org.apache.beam.sdk.schemas.Schema.FieldType; |
@@ -169,6 +170,12 @@ public static boolean isStringType(FieldType fieldType) { |
169 | 170 | FieldType.DATETIME, SqlTypeName.TIMESTAMP, |
170 | 171 | FieldType.STRING, SqlTypeName.VARCHAR); |
171 | 172 |
|
| 173 | + // Associating FieldType to generated RelDataType objects for Beam logical types. Used for |
| 174 | + // recovering the original type in output schema after full Beam FieldType->Calcite Type->Beam |
| 175 | + // FieldType trip |
| 176 | + private static final Map<RelDataType, FieldType> LOGICAL_TYPE_REL_DATA_MAPPING = |
| 177 | + new ConcurrentHashMap<>(); |
| 178 | + |
172 | 179 | /** Generate {@link Schema} from {@code RelDataType} which is used to create table. */ |
173 | 180 | public static Schema toSchema(RelDataType tableInfo) { |
174 | 181 | return tableInfo.getFieldList().stream().map(CalciteUtils::toField).collect(Schema.toSchema()); |
@@ -254,6 +261,9 @@ public static Schema.Field toField(String name, RelDataType calciteType) { |
254 | 261 | } |
255 | 262 |
|
256 | 263 | public static FieldType toFieldType(RelDataType calciteType) { |
| 264 | + if (LOGICAL_TYPE_REL_DATA_MAPPING.containsKey(calciteType)) { |
| 265 | + return LOGICAL_TYPE_REL_DATA_MAPPING.get(calciteType); |
| 266 | + } |
257 | 267 | switch (calciteType.getSqlTypeName()) { |
258 | 268 | case ARRAY: |
259 | 269 | case MULTISET: |
@@ -317,10 +327,27 @@ public static RelDataType toRelDataType(RelDataTypeFactory dataTypeFactory, Fiel |
317 | 327 | return toCalciteRowType(schema, dataTypeFactory); |
318 | 328 | case LOGICAL_TYPE: |
319 | 329 | Schema.LogicalType<?, ?> logicalType = fieldType.getLogicalType(); |
| 330 | + RelDataType relDataType; |
320 | 331 | if (logicalType instanceof PassThroughLogicalType) { |
321 | | - return toRelDataType(dataTypeFactory, logicalType.getBaseType()); |
| 332 | + relDataType = |
| 333 | + toRelDataType( |
| 334 | + dataTypeFactory, logicalType.getBaseType().withNullable(fieldType.getNullable())); |
| 335 | + } else { |
| 336 | + relDataType = dataTypeFactory.createSqlType(toSqlTypeName(fieldType)); |
322 | 337 | } |
323 | | - return dataTypeFactory.createSqlType(toSqlTypeName(fieldType)); |
| 338 | + // For backward-compatibility, exclude logical types registered in |
| 339 | + // CALCITE_TO_BEAM_TYPE_MAPPING, |
| 340 | + // e.g., primitive types, date time types, etc. |
| 341 | + SqlTypeName typeName = relDataType.getSqlTypeName(); |
| 342 | + if (typeName != null && !CALCITE_TO_BEAM_TYPE_MAPPING.containsKey(typeName)) { |
| 343 | + // register both nullable and non-nullable variants. |
| 344 | + boolean flipNullable = !relDataType.isNullable(); |
| 345 | + LOGICAL_TYPE_REL_DATA_MAPPING.put(relDataType, fieldType); |
| 346 | + LOGICAL_TYPE_REL_DATA_MAPPING.put( |
| 347 | + dataTypeFactory.createTypeWithNullability(relDataType, flipNullable), |
| 348 | + fieldType.withNullable(flipNullable)); |
| 349 | + } |
| 350 | + return relDataType; |
324 | 351 | default: |
325 | 352 | return dataTypeFactory.createSqlType(toSqlTypeName(fieldType)); |
326 | 353 | } |
|
0 commit comments