Repository: apache/arrow
File: cpp/src/parquet/arrow/schema.cc (C++)

Code before:
      // Backward-compatibility rule 4
      return GroupToStruct(list_group, current_levels, ctx, out, child_field);
    }

    const auto& repeated_field = list_group.field(0);
    if (!list_group.logical_type()->is_none() || repeated_field->is_repeated()) {
      RETURN_NOT_OK(check_two_level_list_repeated(group));
      if (list_group.logical_type()->is_list()) {
        return ListToSchemaField(list_group, current_levels, ctx, out, child_field);
      } else if (list_group.logical_type()->is_map()) {
        return MapToSchemaField(list_group, current_levels, ctx, out, child_field);
      } else {
        current_levels.Increment(list_group);
        return GroupToStruct(list_group, current_levels, ctx, out, child_field);
      }
    }

    if (group.is_repeated()) {
      return Status::Invalid("LIST-annotated groups must not be repeated.");
    }

    return NodeToSchemaField(*repeated_field, current_levels, ctx, out, child_field);
  }

  RETURN_NOT_OK(check_two_level_list_repeated(group));
  const auto& primitive_node = static_cast<const PrimitiveNode&>(list_node);
  int column_index = ctx->schema->GetColumnIndex(primitive_node);
  ARROW_ASSIGN_OR_RAISE(std::shared_ptr<ArrowType> type,
                        GetTypeForNode(column_index, primitive_node, ctx));
  auto item_field = ::arrow::field(list_node.name(), type, /*nullable=*/false,
                                   FieldIdMetadata(list_node.field_id()));
  return PopulateLeaf(column_index, item_field, current_levels, ctx, out, child_field);
}

Status ListToSchemaField(const GroupNode& group, LevelInfo current_levels,
                         SchemaTreeContext* ctx, const SchemaField* parent,
                         SchemaField* out) {
  if (group.field_count() != 1) {
    return Status::Invalid("LIST-annotated groups must have a single child.");
  }

  if (group.is_optional()) {
    current_levels.IncrementOptional();
  }

  out->children.resize(group.field_count());
  SchemaField* child_field = &out->children[0];

  ctx->LinkParent(out, parent);
  ctx->LinkParent(child_field, out);


Review comment (bug):
In ResolveList(), the backward-compatibility rule 4 branch (logical_type is none + HasListElementName) returns GroupToStruct() without applying the repeated LIST validation. This means a LIST-annotated group that is `REPEATED` could be accepted even when its parent is not LIST-annotated (or parent is null), and for such cases the repetition/definition levels would also be computed incorrectly since ListToSchemaField() no longer increments for `group.is_repeated()`. Apply the same `check_two_level_list_repeated(group)` guard in this branch (and consider treating `parent()==nullptr` as invalid, consistent with MapToSchemaField).

Code after:
    if (list_group.field_count() > 1) {
      // The inner type of the list should be a struct when there are multiple fields
      // in the repeated group
      RETURN_NOT_OK(check_two_level_list_repetition(group));
      return GroupToStruct(list_group, current_levels, ctx, out, child_field);
    }
    if (list_group.field_count() == 0) {
      return Status::Invalid("Group must have at least one child.");
    }

    if (list_group.logical_type()->is_none() && HasListElementName(list_group, group)) {
      // Rule 4 at
      // https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#backward-compatibility-rules
      //
      // required/optional group name=SOMETHING {
      //   repeated group name=array or $SOMETHING_tuple {
      //     required/optional TYPE item;
      //   }
      // }
      //
      // The inner type of the list should be a struct rather than a primitive value
      //
      // yields list<item: struct<item: TYPE ?nullable> not null> ?nullable
      RETURN_NOT_OK(check_two_level_list_repetition(group));
      return GroupToStruct(list_group, current_levels, ctx, out, child_field);
    }

    const auto& repeated_field = list_group.field(0);
    if (!list_group.logical_type()->is_none() || repeated_field->is_repeated()) {
      RETURN_NOT_OK(check_two_level_list_repetition(group));
      if (list_group.logical_type()->is_list()) {
        // Special case where the inner type might be a list with two-level encoding
        // like below:
        //
        // required/optional group name=SOMETHING (LIST) {
        //   repeated group array (LIST) {
        //     repeated TYPE item;
        //   }
        // }
        //
        // yields list<item: list<item: TYPE not null> not null> ?nullable
        return ListToSchemaField(list_group, current_levels, ctx, out, child_field);
      } else if (list_group.logical_type()->is_map()) {
        return MapToSchemaField(list_group, current_levels, ctx, out, child_field);
      } else {
        return GroupToStruct(list_group, current_levels, ctx, out, child_field);
      }
    }

    // Resolve normal 3-level encoding
    //