| 164 | } |
| 165 | |
| 166 | Status Finalize(compute::KernelContext* ctx, Datum* out) override { |
| 167 | auto state = |
| 168 | arrow::internal::checked_cast<PythonUdfScalarAggregatorImpl*>(ctx->state()); |
| 169 | const int num_args = input_schema->num_fields(); |
| 170 | |
| 171 | // Note: The way that batches are concatenated together |
| 172 | // would result in using double amount of the memory. |
| 173 | // This is OK for now because non decomposable aggregate |
| 174 | // UDF is supposed to be used with segmented aggregation |
| 175 | // where the size of the segment is more or less constant |
| 176 | // so doubling that is not a big deal. This can be also |
| 177 | // improved in the future to use more efficient way to |
| 178 | // concatenate. |
| 179 | ARROW_ASSIGN_OR_RAISE(auto table, |
| 180 | arrow::Table::FromRecordBatches(input_schema, values)); |
| 181 | ARROW_ASSIGN_OR_RAISE(table, table->CombineChunks(ctx->memory_pool())); |
| 182 | UdfContext udf_context{ctx->memory_pool(), table->num_rows()}; |
| 183 | |
| 184 | if (table->num_rows() == 0) { |
| 185 | return Status::Invalid("Finalized is called with empty inputs"); |
| 186 | } |
| 187 | |
| 188 | RETURN_NOT_OK(SafeCallIntoPython([&] { |
| 189 | std::unique_ptr<OwnedRef> result; |
| 190 | OwnedRef arg_tuple(PyTuple_New(num_args)); |
| 191 | RETURN_NOT_OK(CheckPyError()); |
| 192 | |
| 193 | for (int arg_id = 0; arg_id < num_args; arg_id++) { |
| 194 | // Since we combined chunks there is only one chunk |
| 195 | std::shared_ptr<Array> c_data = table->column(arg_id)->chunk(0); |
| 196 | PyObject* data = wrap_array(c_data); |
| 197 | PyTuple_SetItem(arg_tuple.obj(), arg_id, data); |
| 198 | } |
| 199 | result = |
| 200 | std::make_unique<OwnedRef>(cb(function->obj(), udf_context, arg_tuple.obj())); |
| 201 | RETURN_NOT_OK(CheckPyError()); |
| 202 | // unwrapping the output for expected output type |
| 203 | if (is_scalar(result->obj())) { |
| 204 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<Scalar> val, unwrap_scalar(result->obj())); |
| 205 | if (*output_type != *val->type) { |
| 206 | return Status::TypeError("Expected output datatype ", output_type->ToString(), |
| 207 | ", but function returned datatype ", |
| 208 | val->type->ToString()); |
| 209 | } |
| 210 | out->value = std::move(val); |
| 211 | return Status::OK(); |
| 212 | } |
| 213 | return Status::TypeError("Unexpected output type: ", |
| 214 | Py_TYPE(result->obj())->tp_name, " (expected Scalar)"); |
| 215 | })); |
| 216 | return Status::OK(); |
| 217 | } |
| 218 | |
| 219 | std::shared_ptr<OwnedRefNoGIL> function; |
| 220 | UdfWrapperCallback cb; |
nothing calls this directly
no test coverage detected