mxtymoshyk commented on code in PR #40841:
URL: https://github.com/apache/beam/pull/40841#discussion_r4387819451


##########
sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/rel/BeamUnnestRel.java:
##########
@@ -136,32 +133,41 @@ private UnnestFn(Schema outputSchema, List<Integer> 
unnestIndices) {
     }
 
     /**
-     * This is recursive call to get all the values of the nested rows. The 
recursion is bounded by
-     * the amount of nesting with in the data. This mirrors the unnest 
behavior of calcite towards
-     * schema. *
+     * Appends the fields of an unnested array element to {@code values}, laid 
out the way {@code
+     * outputSchema} expects them. The recursion is bounded by the nesting 
depth of the element
+     * schema.
+     *
+     * <p>Whether a nested row field of the element arrives as one ROW column 
or as one column per
+     * leaf field depends on which Calcite planner produced the plan. For an 
element {@code ROW<id,
+     * author ROW<name, email>>}, the planner behind {@code SqlTransform} 
flattens the element into
+     * {@code id, name, email}, while the planner behind the JDBC driver and 
the SQL shell keeps
+     * {@code id, author}. Building the values from the element schema alone 
matches only one of
+     * them, and the other then fails in {@link Row.Builder#build()} with a 
field count mismatch. So
+     * each nested row is checked against the output column at the same 
position: a ROW column takes
+     * the row as is, any other column means the row was flattened.
      */
-    private List<Object> getNestedRowBaseValues(Row nestedRow) {
-      return IntStream.range(0, nestedRow.getFieldCount())
-          .mapToObj(
-              (i) -> {
-                List<Object> values = new ArrayList<>();
-                Schema.FieldType fieldType = 
nestedRow.getSchema().getField(i).getType();
-                if (fieldType.getTypeName().equals(Schema.TypeName.ROW)) {
-                  @Nullable Row row = nestedRow.getBaseValue(i, Row.class);
-                  if (row == null) {
-                    return Stream.builder().build();
-                  }
-                  List<Object> rowValues = getNestedRowBaseValues(row);
-                  if (null != rowValues) {
-                    values.addAll(rowValues);
-                  }
-                } else {
-                  values.add(nestedRow.getBaseValue(i));
-                }
-                return values.stream();
-              })
-          .flatMap(Function.identity())
-          .collect(Collectors.toList());
+    private void addElementValues(
+        Schema elementSchema, @Nullable Row element, List<Object> values) {
+      for (int i = 0; i < elementSchema.getFieldCount(); i++) {
+        Schema.FieldType fieldType = elementSchema.getField(i).getType();
+        Object value = null;
+        if (Objects.nonNull(element)) {

Review Comment:
   Done in 390bfad. Switched to `element != null` to match the rest of the 
module.



##########
sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/rel/BeamUnnestRel.java:
##########
@@ -136,32 +133,41 @@ private UnnestFn(Schema outputSchema, List<Integer> 
unnestIndices) {
     }
 
     /**
-     * This is recursive call to get all the values of the nested rows. The 
recursion is bounded by
-     * the amount of nesting with in the data. This mirrors the unnest 
behavior of calcite towards
-     * schema. *
+     * Appends the fields of an unnested array element to {@code values}, laid 
out the way {@code
+     * outputSchema} expects them. The recursion is bounded by the nesting 
depth of the element
+     * schema.
+     *
+     * <p>Whether a nested row field of the element arrives as one ROW column 
or as one column per
+     * leaf field depends on which Calcite planner produced the plan. For an 
element {@code ROW<id,
+     * author ROW<name, email>>}, the planner behind {@code SqlTransform} 
flattens the element into
+     * {@code id, name, email}, while the planner behind the JDBC driver and 
the SQL shell keeps
+     * {@code id, author}. Building the values from the element schema alone 
matches only one of
+     * them, and the other then fails in {@link Row.Builder#build()} with a 
field count mismatch. So
+     * each nested row is checked against the output column at the same 
position: a ROW column takes
+     * the row as is, any other column means the row was flattened.
      */
-    private List<Object> getNestedRowBaseValues(Row nestedRow) {
-      return IntStream.range(0, nestedRow.getFieldCount())
-          .mapToObj(
-              (i) -> {
-                List<Object> values = new ArrayList<>();
-                Schema.FieldType fieldType = 
nestedRow.getSchema().getField(i).getType();
-                if (fieldType.getTypeName().equals(Schema.TypeName.ROW)) {
-                  @Nullable Row row = nestedRow.getBaseValue(i, Row.class);
-                  if (row == null) {
-                    return Stream.builder().build();
-                  }
-                  List<Object> rowValues = getNestedRowBaseValues(row);
-                  if (null != rowValues) {
-                    values.addAll(rowValues);
-                  }
-                } else {
-                  values.add(nestedRow.getBaseValue(i));
-                }
-                return values.stream();
-              })
-          .flatMap(Function.identity())
-          .collect(Collectors.toList());
+    private void addElementValues(
+        Schema elementSchema, @Nullable Row element, List<Object> values) {
+      for (int i = 0; i < elementSchema.getFieldCount(); i++) {
+        Schema.FieldType fieldType = elementSchema.getField(i).getType();
+        Object value = null;
+        if (Objects.nonNull(element)) {
+          value = element.getBaseValue(i);
+        }
+
+        boolean isRow = fieldType.getTypeName().equals(Schema.TypeName.ROW);
+        if (!isRow || isRowColumn(values.size())) {
+          values.add(value);
+          continue;
+        }
+
+        // a null nested row still fills one null per flattened column
+        addElementValues(fieldType.getRowSchema(), (Row) value, values);
+      }
+    }
+
+    private boolean isRowColumn(int index) {

Review Comment:
   Added a guard in 7a2a631, with one difference from your snippet. Past the 
last output column, `isRowColumn` returns false, so the extra values reach 
`Row.Builder#build()` and fail with the same `Row expected N fields ... 
initialized with M fields` error this issue reports. That message lists the 
output schema fields, which helps more when tracing a planner mismatch than a 
bare index. I can switch to an explicit `IllegalStateException` if you prefer 
that.



##########
sdks/java/extensions/sql/src/test/java/org/apache/beam/sdk/extensions/sql/impl/JdbcDriverTest.java:
##########
@@ -351,6 +351,59 @@ public void testSelectsFromExistingComplexTable() throws 
Exception {
     assertThat(resultRows, containsInAnyOrder(row(1L, "aaa"), row(2L, "bbb")));
   }
 
+  @Test

Review Comment:
   Added in 7a2a631. The JDBC test now has a third element with a null 
`nestedRow`, and the query returns null for `item.nestedRow.name` on that row.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to