Repository: apache/arrow
File: cpp/src/arrow/compute/kernels/scalar_cast_string.cc (C++)

Code before:

  std::shared_ptr<ArrayData> input_arr = input.ToArrayData();
  ArrayData* output = out->array_data().get();
  output->length = input_arr->length;
  output->SetNullCount(input_arr->null_count);
  output->buffers = std::move(input_arr->buffers);
  output->child_data = std::move(input_arr->child_data);

  if (output->buffers[0]) {
    // If reusing the null bitmap, ensure offset into the first byte is the same as input.
    output->offset = input_arr->offset % 8;
    output->buffers[0] = SliceBuffer(output->buffers[0], input_arr->offset / 8);
  } else {
    output->offset = 0;
  }

  return CastBinaryToBinaryOffsets<typename I::offset_type, typename O::offset_type>(
      ctx, input, out->array_data().get());
}

// String View -> Offset String
template <typename O, typename I>
enable_if_t<is_binary_view_like_type<I>::value && is_base_binary_type<O>::value, Status>
BinaryToBinaryCastExec(KernelContext* ctx, const ExecSpan& batch, ExecResult* out) {
  using offset_type = typename O::offset_type;
  using DataBuilder = TypedBufferBuilder<uint8_t>;
  using OffsetBuilder = TypedBufferBuilder<offset_type>;
  const CastOptions& options = checked_cast<const CastState&>(*ctx->state()).options;
  const ArraySpan& input = batch[0].array;

  if constexpr (!I::is_utf8 && O::is_utf8) {
    if (!options.allow_invalid_utf8) {
      InitializeUTF8();
      ArraySpanVisitor<I> visitor;
      Utf8Validator validator;
      RETURN_NOT_OK(visitor.Visit(input, &validator));
    }
  }

  ArrayData* output = out->array_data().get();
  output->length = input.length;
  output->SetNullCount(input.null_count);

  // Set up validity bitmap
  ARROW_ASSIGN_OR_RAISE(output->buffers[0],
                        GetOrCopyNullBitmapBuffer(input, ctx->memory_pool()));

  // Set up offset and data buffer
  OffsetBuilder offset_builder(ctx->memory_pool());
  RETURN_NOT_OK(offset_builder.Reserve(input.length + 1));
  offset_builder.UnsafeAppend(0);  // offsets start at 0

Review comment (performance):
We need to make sure all cases are covered correctly here. `enable_if_t<is_base_binary_type<I>::value && is_base_binary_type<O>::value, Status>` covers all pairs of `Binary/LargeBinary/String/LargeString`.

`if constexpr (!I::is_utf8 && O::is_utf8) {` handles the UTF8 validation in case we are going from `{Binary, LargeBinary}` to `{String, LargeString}`.

The allocation issue is only a problem when going from 32 to 64 or 64 to 32, so you can shape your new code like this:

```cpp
if constexpr (sizeof(typename I::offset_type) != sizeof(typename O::offset_type)) {
  std::shared_ptr<ArrayData> input_arr = input.ToArrayData();
  ArrayData* output = out->array_data().get();
  // ...
  if (output->buffers[0]) {
    ...


  return CastBinaryToBinaryOffsets<typename I::offset_type, typename O::offset_type>(
        ctx, input, out->array_data().get()); 
} else {
  return ZeroCopyCastExec(ctx, batch, out);
}
```
----

Useful diagram I made some time ago: https://gist.github.com/felipecrv/3c02f3784221d946dec1b031c6d400db

Code after:
  }

  // Start with a zero-copy cast, but change indices to the correct size and set validity
  // bitmap and offset if needed.
  RETURN_NOT_OK(ZeroCopyCastExec(ctx, batch, out));
  if constexpr (sizeof(typename I::offset_type) != sizeof(typename O::offset_type)) {
    std::shared_ptr<ArrayData> input_arr = input.ToArrayData();
    ArrayData* output = out->array_data().get();

    // Slice buffers to minimize the output's offset. We need a small offset because
    // CastBinaryToBinaryOffsets() will reallocate the offsets buffer with size
    // (out_length + out_offset + 1) * sizeof(offset_type).
    int64_t input_offset = input_arr->offset;
    size_t input_offset_type_size = sizeof(typename I::offset_type);
    if (output->null_count != 0 && output->buffers[0]) {
      // Avoid reallocation of the validity buffer by allowing some padding bits
      output->offset = input_offset % 8;
    } else {
      output->offset = 0;
    }
    if (output->buffers[0]) {
      output->buffers[0] = SliceBuffer(output->buffers[0], input_offset / 8);
    }
    output->buffers[1] = SliceBuffer(
        output->buffers[1], (input_offset - output->offset) * input_offset_type_size);

    return CastBinaryToBinaryOffsets<typename I::offset_type, typename O::offset_type>(
        ctx, input, out->array_data().get());
  }
  return Status::OK();
}

// String View -> Offset String
template <typename O, typename I>
enable_if_t<is_binary_view_like_type<I>::value && is_base_binary_type<O>::value, Status>
BinaryToBinaryCastExec(KernelContext* ctx, const ExecSpan& batch, ExecResult* out) {
  using offset_type = typename O::offset_type;
  using DataBuilder = TypedBufferBuilder<uint8_t>;
  using OffsetBuilder = TypedBufferBuilder<offset_type>;
  const CastOptions& options = checked_cast<const CastState&>(*ctx->state()).options;
  const ArraySpan& input = batch[0].array;

  if constexpr (!I::is_utf8 && O::is_utf8) {
    if (!options.allow_invalid_utf8) {
      InitializeUTF8();
      ArraySpanVisitor<I> visitor;
      Utf8Validator validator;
      RETURN_NOT_OK(visitor.Visit(input, &validator));
    }
  }