| 119 | } |
| 120 | |
| 121 | arrow::Result<std::vector<data_row>> ColumnarTableToVector( |
| 122 | const std::shared_ptr<arrow::Table>& table) { |
| 123 | // To convert an Arrow table back into the same row-wise representation as in the |
| 124 | // above section, we first will check that the table conforms to our expected |
| 125 | // schema and then will build up the vector of rows incrementally. |
| 126 | // |
| 127 | // For the check if the table is as expected, we can utilise solely its schema. |
| 128 | std::vector<std::shared_ptr<arrow::Field>> schema_vector = { |
| 129 | arrow::field("id", arrow::int64()), arrow::field("components", arrow::int64()), |
| 130 | arrow::field("component_cost", arrow::list(arrow::float64()))}; |
| 131 | auto expected_schema = std::make_shared<arrow::Schema>(schema_vector); |
| 132 | |
| 133 | if (!expected_schema->Equals(*table->schema())) { |
| 134 | // The table doesn't have the expected schema thus we cannot directly |
| 135 | // convert it to our target representation. |
| 136 | return arrow::Status::Invalid("Schemas are not matching!"); |
| 137 | } |
| 138 | |
| 139 | // As we have ensured that the table has the expected structure, we can unpack the |
| 140 | // underlying arrays. For the primitive columns `id` and `components` we can use the |
| 141 | // high level functions to get the values whereas for the nested column |
| 142 | // `component_costs` we need to access the C-pointer to the data to copy its |
| 143 | // contents into the resulting `std::vector<double>`. Here we need to be careful to |
| 144 | // also add the offset to the pointer. This offset is needed to enable zero-copy |
| 145 | // slicing operations. While this could be adjusted automatically for double |
| 146 | // arrays, this cannot be done for the accompanying bitmap as often the slicing |
| 147 | // border would be inside a byte. |
| 148 | |
| 149 | auto ids = std::static_pointer_cast<arrow::Int64Array>(table->column(0)->chunk(0)); |
| 150 | auto components = |
| 151 | std::static_pointer_cast<arrow::Int64Array>(table->column(1)->chunk(0)); |
| 152 | auto component_cost = |
| 153 | std::static_pointer_cast<arrow::ListArray>(table->column(2)->chunk(0)); |
| 154 | auto component_cost_values = |
| 155 | std::static_pointer_cast<arrow::DoubleArray>(component_cost->values()); |
| 156 | // To enable zero-copy slices, the native values pointer might need to account |
| 157 | // for this slicing offset. This is not needed for the higher level functions |
| 158 | // like Value(…) that already account for this offset internally. |
| 159 | const double* ccv_ptr = component_cost_values->raw_values(); |
| 160 | std::vector<data_row> rows; |
| 161 | for (int64_t i = 0; i < table->num_rows(); i++) { |
| 162 | // Another simplification in this example is that we assume that there are |
| 163 | // no null entries, e.g. each row is fill with valid values. |
| 164 | int64_t id = ids->Value(i); |
| 165 | int64_t component = components->Value(i); |
| 166 | const double* first = ccv_ptr + component_cost->value_offset(i); |
| 167 | const double* last = ccv_ptr + component_cost->value_offset(i + 1); |
| 168 | std::vector<double> components_vec(first, last); |
| 169 | rows.push_back({id, component, components_vec}); |
| 170 | } |
| 171 | |
| 172 | return rows; |
| 173 | } |
| 174 | |
| 175 | arrow::Status RunRowConversion() { |
| 176 | std::vector<data_row> original_rows = { |
no test coverage detected