| 662 | } |
| 663 | |
| 664 | Status ConvertStruct(PandasOptions options, const ChunkedArray& data, |
| 665 | PyObject** out_values) { |
| 666 | if (data.num_chunks() == 0) { |
| 667 | return Status::OK(); |
| 668 | } |
| 669 | // ChunkedArray has at least one chunk |
| 670 | auto arr = checked_cast<const StructArray*>(data.chunk(0).get()); |
| 671 | // Use it to cache the struct type and number of fields for all chunks |
| 672 | int32_t num_fields = arr->num_fields(); |
| 673 | auto array_type = arr->type(); |
| 674 | std::vector<OwnedRef> fields_data(num_fields * data.num_chunks()); |
| 675 | OwnedRef dict_item; |
| 676 | |
| 677 | // See notes in MakeInnerOptions. |
| 678 | options = MakeInnerOptions(std::move(options)); |
| 679 | // Don't blindly convert because timestamps in lists are handled differently. |
| 680 | options.timestamp_as_object = true; |
| 681 | |
| 682 | for (int c = 0; c < data.num_chunks(); c++) { |
| 683 | auto fields_data_offset = c * num_fields; |
| 684 | auto arr = checked_cast<const StructArray*>(data.chunk(c).get()); |
| 685 | // Convert the struct arrays first |
| 686 | for (int32_t i = 0; i < num_fields; i++) { |
| 687 | auto field = arr->field(static_cast<int>(i)); |
| 688 | // In case the field is an extension array, use .storage() to convert to Pandas |
| 689 | if (field->type()->id() == Type::EXTENSION) { |
| 690 | const ExtensionArray& arr_ext = checked_cast<const ExtensionArray&>(*field); |
| 691 | field = arr_ext.storage(); |
| 692 | } |
| 693 | RETURN_NOT_OK(ConvertArrayToPandas(options, field, nullptr, |
| 694 | fields_data[i + fields_data_offset].ref())); |
| 695 | ARROW_DCHECK(PyArray_Check(fields_data[i + fields_data_offset].obj())); |
| 696 | } |
| 697 | |
| 698 | // Construct a dictionary for each row |
| 699 | const bool has_nulls = data.null_count() > 0; |
| 700 | for (int64_t i = 0; i < arr->length(); ++i) { |
| 701 | if (has_nulls && arr->IsNull(i)) { |
| 702 | Py_INCREF(Py_None); |
| 703 | *out_values = Py_None; |
| 704 | } else { |
| 705 | // Build the new dict object for the row |
| 706 | dict_item.reset(PyDict_New()); |
| 707 | RETURN_IF_PYERROR(); |
| 708 | for (int32_t field_idx = 0; field_idx < num_fields; ++field_idx) { |
| 709 | OwnedRef field_value; |
| 710 | auto name = array_type->field(static_cast<int>(field_idx))->name(); |
| 711 | if (!arr->field(static_cast<int>(field_idx))->IsNull(i)) { |
| 712 | // Value exists in child array, obtain it |
| 713 | auto array = reinterpret_cast<PyArrayObject*>( |
| 714 | fields_data[field_idx + fields_data_offset].obj()); |
| 715 | auto ptr = reinterpret_cast<const char*>(PyArray_GETPTR1(array, i)); |
| 716 | field_value.reset(PyArray_GETITEM(array, ptr)); |
| 717 | RETURN_IF_PYERROR(); |
| 718 | } else { |
| 719 | // Translate the Null to a None |
| 720 | Py_INCREF(Py_None); |
| 721 | field_value.reset(Py_None); |
no test coverage detected