|
36 | 36 | import org.apache.beam.sdk.transforms.windowing.Trigger; |
37 | 37 | import org.apache.beam.sdk.transforms.windowing.Window; |
38 | 38 | import org.apache.beam.sdk.transforms.windowing.WindowFn; |
| 39 | +import org.apache.beam.sdk.util.Preconditions; |
39 | 40 | import org.apache.beam.sdk.values.PCollection; |
40 | 41 | import org.apache.beam.sdk.values.PCollectionList; |
41 | 42 | import org.apache.beam.sdk.values.Row; |
@@ -193,11 +194,41 @@ private PCollection<Row> standardJoin( |
193 | 194 | } |
194 | 195 |
|
195 | 196 | // Flatten the lhs and rhs fields into a single row. |
| 197 | + FieldAccessDescriptor flattenFields = |
| 198 | + FieldAccessDescriptor.withFieldNames( |
| 199 | + org.apache.beam.sdk.schemas.transforms.Join.LHS_TAG + ".*", |
| 200 | + org.apache.beam.sdk.schemas.transforms.Join.RHS_TAG + ".*"); |
| 201 | + |
| 202 | + // Reconcile the desired output schema (which carries the Calcite-derived field names and the |
| 203 | + // correct top-level, outer-join-aware nullability) with the types the data actually carries. |
| 204 | + // Calcite's join row-type derivation can report a different nullability than the rows hold -- |
| 205 | + // in particular it can mark the fields nested inside a struct column as nullable even when the |
| 206 | + // joined rows still keep them NOT NULL. Forcing the Calcite schema verbatim then trips Select's |
| 207 | + // type-equality guard. The flatten emits the lhs struct's fields followed by the rhs struct's |
| 208 | + // fields, so walk the Calcite output positionally against those data fields, keeping Calcite's |
| 209 | + // names and top-level nullability but adopting the data's (possibly deeper) field types. |
| 210 | + Schema calciteSchema = CalciteUtils.toSchema(getRowType()); |
| 211 | + Schema joinedSchema = joinedRows.getSchema(); |
| 212 | + List<Schema.Field> dataFields = new java.util.ArrayList<>(); |
| 213 | + dataFields.addAll( |
| 214 | + Preconditions.checkArgumentNotNull(joinedSchema.getField(0).getType().getRowSchema()) |
| 215 | + .getFields()); |
| 216 | + dataFields.addAll( |
| 217 | + Preconditions.checkArgumentNotNull(joinedSchema.getField(1).getType().getRowSchema()) |
| 218 | + .getFields()); |
| 219 | + Schema.Builder reconciled = Schema.builder(); |
| 220 | + for (int i = 0; i < calciteSchema.getFieldCount(); i++) { |
| 221 | + Schema.Field calciteField = calciteSchema.getField(i); |
| 222 | + // Keep the data's type verbatim -- including the nullability of fields nested inside struct |
| 223 | + // columns, which Calcite's join row-type derivation can report incorrectly -- but honour |
| 224 | + // Calcite's top-level nullability so outer-join columns stay nullable. |
| 225 | + reconciled.addField( |
| 226 | + calciteField.withType( |
| 227 | + dataFields.get(i).getType().withNullable(calciteField.getType().getNullable()))); |
| 228 | + } |
| 229 | + |
196 | 230 | return joinedRows.apply( |
197 | | - Select.<Row>fieldNames( |
198 | | - org.apache.beam.sdk.schemas.transforms.Join.LHS_TAG + ".*", |
199 | | - org.apache.beam.sdk.schemas.transforms.Join.RHS_TAG + ".*") |
200 | | - .withOutputSchema(CalciteUtils.toSchema(getRowType()))); |
| 231 | + Select.<Row>fieldAccess(flattenFields).withOutputSchema(reconciled.build())); |
201 | 232 | } |
202 | 233 |
|
203 | 234 | @Override |
|
0 commit comments