diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt index eaa6b325cc6..f8d4cd4286d 100644 --- a/cpp/src/arrow/CMakeLists.txt +++ b/cpp/src/arrow/CMakeLists.txt @@ -382,6 +382,7 @@ if(ARROW_COMPUTE) compute/kernels/scalar_string.cc compute/kernels/scalar_validity.cc compute/kernels/scalar_fill_null.cc + compute/kernels/scalar_replace.cc compute/kernels/util_internal.cc compute/kernels/vector_hash.cc compute/kernels/vector_nested.cc diff --git a/cpp/src/arrow/compute/api_scalar.cc b/cpp/src/arrow/compute/api_scalar.cc index 671c8246378..3034ac0b7b8 100644 --- a/cpp/src/arrow/compute/api_scalar.cc +++ b/cpp/src/arrow/compute/api_scalar.cc @@ -140,5 +140,10 @@ Result FillNull(const Datum& values, const Datum& fill_value, ExecContext return CallFunction("fill_null", {values, fill_value}, ctx); } +Result Replace(const Datum& values, const Datum& mask, const Datum& replacement, + ExecContext* ctx) { + return CallFunction("replace", {values, mask, replacement}, ctx); +} + } // namespace compute } // namespace arrow diff --git a/cpp/src/arrow/compute/api_scalar.h b/cpp/src/arrow/compute/api_scalar.h index 37f3077e4bd..f0bbfe5bab6 100644 --- a/cpp/src/arrow/compute/api_scalar.h +++ b/cpp/src/arrow/compute/api_scalar.h @@ -388,5 +388,21 @@ ARROW_EXPORT Result FillNull(const Datum& values, const Datum& fill_value, ExecContext* ctx = NULLPTR); +/// \brief Replace replaces each element in `values` for which mask bit is true +/// with `fill_value` +/// +/// \param[in] values input to replace based on mask +/// \param[in] mask bits +/// \param[in] replacement scalar +/// \param[in] ctx the function execution context, optional +/// +/// \return the resulting datum +/// +/// \since X.X.X +/// \note API not yet finalized +ARROW_EXPORT +Result Replace(const Datum& values, const Datum& mask, const Datum& replacement, + ExecContext* ctx = NULLPTR); + } // namespace compute } // namespace arrow diff --git a/cpp/src/arrow/compute/kernels/CMakeLists.txt b/cpp/src/arrow/compute/kernels/CMakeLists.txt index 577b250da87..4383ceb8b00 100644 --- a/cpp/src/arrow/compute/kernels/CMakeLists.txt +++ b/cpp/src/arrow/compute/kernels/CMakeLists.txt @@ -29,6 +29,7 @@ add_arrow_compute_test(scalar_test scalar_string_test.cc scalar_validity_test.cc scalar_fill_null_test.cc + scalar_replace_test.cc test_util.cc) add_arrow_benchmark(scalar_arithmetic_benchmark PREFIX "arrow-compute") diff --git a/cpp/src/arrow/compute/kernels/scalar_replace.cc b/cpp/src/arrow/compute/kernels/scalar_replace.cc new file mode 100644 index 00000000000..13b27d52a19 --- /dev/null +++ b/cpp/src/arrow/compute/kernels/scalar_replace.cc @@ -0,0 +1,309 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "arrow/compute/kernels/common.h" +#include "arrow/util/bit_block_counter.h" +#include "arrow/util/bit_util.h" +#include "arrow/util/bitmap_ops.h" + +namespace arrow { + +using internal::BitBlockCount; +using internal::BitBlockCounter; + +namespace compute { +namespace internal { + +namespace { + +void handle_nulls(KernelContext* ctx, const ArrayData& data, const ArrayData& mask, + ArrayData* output) { + if (data.MayHaveNulls()) { + KERNEL_ASSIGN_OR_RAISE(std::shared_ptr out_nulls, ctx, + ctx->AllocateBitmap(data.length)); + + if (mask.MayHaveNulls()) { + ::arrow::internal::BitmapAnd(mask.buffers[0]->data(), mask.offset, + mask.buffers[1]->data(), mask.offset, mask.length, + output->offset, out_nulls->mutable_data()); + ::arrow::internal::BitmapOr(data.buffers[0]->data(), data.offset, out_nulls->data(), + output->offset, data.length, output->offset, + out_nulls->mutable_data()); + } else { + ::arrow::internal::BitmapOr(data.buffers[0]->data(), data.offset, + mask.buffers[1]->data(), mask.offset, mask.length, + output->offset, out_nulls->mutable_data()); + } + + if (::arrow::internal::CountSetBits(out_nulls->data(), output->offset, data.length) < + data.length) + output->buffers[0] = out_nulls; + } +} + +template +struct ReplaceFunctor {}; + +// Numeric inputs + +template +struct ReplaceFunctor::value>> { + using T = typename TypeTraits::CType; + + static void Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) { + const ArrayData& data = *batch[0].array(); + const ArrayData& mask = *batch[1].array(); + const Scalar& replacement = *batch[2].scalar(); + ArrayData* output = out->mutable_array(); + + // Ensure the kernel is configured properly to have no validity bitmap / + // null count 0 unless we explicitly propagate it below. + DCHECK(output->buffers[0] == nullptr); + + if (replacement.is_valid) { + handle_nulls(ctx, data, mask, output); + + KERNEL_ASSIGN_OR_RAISE(std::shared_ptr out_buf, ctx, + ctx->Allocate(data.length * sizeof(T))); + T value = UnboxScalar::Unbox(replacement); + const uint8_t* to_replace = mask.buffers[1]->data(); + const T* in_values = data.GetValues(1); + T* out_values = reinterpret_cast(out_buf->mutable_data()); + int64_t offset = data.offset; + BitBlockCounter bit_counter(to_replace, data.offset, data.length); + while (offset < data.offset + data.length) { + BitBlockCount block = bit_counter.NextWord(); + if (block.NoneSet()) { + std::memcpy(out_values, in_values, block.length * sizeof(T)); + } else if (block.AllSet()) { + std::fill(out_values, out_values + block.length, value); + } else { + for (int64_t i = 0; i < block.length; ++i) { + out_values[i] = + BitUtil::GetBit(to_replace, offset + i) ? value : in_values[i]; + } + } + offset += block.length; + out_values += block.length; + in_values += block.length; + } + output->buffers[1] = out_buf; + } else { + *output = data; + } + } +}; + +// Boolean input + +template +struct ReplaceFunctor::value>> { + static void Exec(KernelContext* ctx, const ExecBatch batch, Datum* out) { + const ArrayData& data = *batch[0].array(); + const ArrayData& mask = *batch[1].array(); + const Scalar& replacement = *batch[2].scalar(); + ArrayData* output = out->mutable_array(); + + // Ensure the kernel is configured properly to have no validity bitmap / + // null count 0 unless we explicitly propagate it below. + DCHECK(output->buffers[0] == nullptr); + + bool value = UnboxScalar::Unbox(replacement); + if (replacement.is_valid) { + handle_nulls(ctx, data, mask, output); + + KERNEL_ASSIGN_OR_RAISE(std::shared_ptr out_buf, ctx, + ctx->AllocateBitmap(data.length)); + const uint8_t* to_replace = mask.buffers[1]->data(); + const uint8_t* data_bitmap = data.buffers[1]->data(); + uint8_t* out_bitmap = out_buf->mutable_data(); + + int64_t data_offset = data.offset; + BitBlockCounter bit_counter(to_replace, data.offset, data.length); + + int64_t out_offset = 0; + while (out_offset < data.length) { + BitBlockCount block = bit_counter.NextWord(); + if (block.NoneSet()) { + ::arrow::internal::CopyBitmap(data_bitmap, data_offset, block.length, + out_bitmap, out_offset); + } else if (block.AllSet()) { + BitUtil::SetBitsTo(out_bitmap, out_offset, block.length, value); + } else { + for (int64_t i = 0; i < block.length; ++i) { + BitUtil::SetBitTo(out_bitmap, out_offset + i, + BitUtil::GetBit(to_replace, data_offset + i) + ? value + : BitUtil::GetBit(data_bitmap, data_offset + i)); + } + } + data_offset += block.length; + out_offset += block.length; + } + output->buffers[1] = out_buf; + } else { + *output = data; + } + } +}; + +// Null input + +template +struct ReplaceFunctor::value>> { + static void Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) { + // Nothing preallocated, so we assign into the output + *out->mutable_array() = *batch[0].array(); + } +}; + +// Binary-like + +template +struct ReplaceFunctor::value>> { + using BuilderType = typename TypeTraits::BuilderType; + using OffsetType = typename Type::offset_type; + + static void Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) { + const ArrayData& input = *batch[0].array(); + const ArrayData& mask = *batch[1].array(); + const auto& replacement_scalar = + checked_cast(*batch[2].scalar()); + util::string_view replacement(*replacement_scalar.value); + ArrayData* output = out->mutable_array(); + + // Ensure the kernel is configured properly to have no validity bitmap / + // null count 0 unless we explicitly propagate it below. + DCHECK(output->buffers[0] == nullptr); + + const uint8_t* mask_validities = + mask.buffers[0] == nullptr ? nullptr : mask.buffers[0]->data(); + const std::shared_ptr to_replace = mask.buffers[1]; + std::shared_ptr to_replace_valid = nullptr; + uint64_t replace_count = 0; + { + if (mask_validities == nullptr) { + to_replace_valid = to_replace; + } else { + KERNEL_ASSIGN_OR_RAISE( + to_replace_valid, ctx, + ::arrow::internal::BitmapAnd(ctx->memory_pool(), mask_validities, mask.offset, + to_replace->data(), mask.offset, mask.length, + output->offset)); + } + BitBlockCounter bit_counter(to_replace_valid->data(), input.offset, input.length); + int64_t i = 0; + while (i < input.length) { + BitBlockCount block = bit_counter.NextWord(); + replace_count += block.popcount; + i += block.length; + } + } + + if (replace_count > 0 && replacement_scalar.is_valid) { + const uint8_t* input_validities = + input.buffers[0] == nullptr ? nullptr : input.buffers[0]->data(); + const auto input_offsets = input.GetValues(1, input.offset); + // offset is 0 otherwise GetValue() will "shift" the buffer by input.offset bytes + // (should it rather shift by the lengths of the first input.offset string values ?) + const auto input_values = input.GetValues(2, 0); + BuilderType builder(input.type, ctx->memory_pool()); + KERNEL_RETURN_IF_ERROR( + ctx, builder.ReserveData(input.buffers[2]->size() - input_offsets[0] + + replace_count * replacement.length())); + KERNEL_RETURN_IF_ERROR(ctx, builder.Resize(input.length)); + + BitBlockCounter bit_counter(to_replace_valid->data(), input.offset, input.length); + int64_t j = 0; + while (j < input.length) { + BitBlockCount block = bit_counter.NextWord(); + for (int64_t i = 0; i < block.length; ++i) { + if (BitUtil::GetBit(to_replace_valid->data(), input.offset + j + i)) { + builder.UnsafeAppend(replacement); + } else { + if (input_validities == nullptr || + BitUtil::GetBit(input_validities, input.offset + j + i)) { + auto current_offset = input_offsets[j + i]; + auto next_offset = input_offsets[j + i + 1]; + auto string_value = util::string_view(input_values + current_offset, + next_offset - current_offset); + builder.UnsafeAppend(string_value); + } else { + builder.UnsafeAppendNull(); + } + } + } + j += block.length; + } + std::shared_ptr string_array; + KERNEL_RETURN_IF_ERROR(ctx, builder.Finish(&string_array)); + *output = *string_array->data(); + // The builder does not match the logical type, due to + // GenerateTypeAgnosticVarBinaryBase + output->type = input.type; + } else { + *output = input; + } + } +}; + +void AddBasicReplaceKernels(ScalarKernel kernel, ScalarFunction* func) { + auto AddKernels = [&](const std::vector>& types) { + for (const std::shared_ptr& ty : types) { + kernel.signature = KernelSignature::Make( + {InputType::Array(ty), InputType::Array(boolean()), InputType::Scalar(ty)}, ty); + kernel.exec = GenerateTypeAgnosticPrimitive(*ty); + DCHECK_OK(func->AddKernel(kernel)); + } + }; + AddKernels(NumericTypes()); + AddKernels(TemporalTypes()); + AddKernels({boolean(), null()}); +} + +void AddBinaryReplaceKernels(ScalarKernel kernel, ScalarFunction* func) { + for (const std::shared_ptr& ty : BaseBinaryTypes()) { + kernel.signature = KernelSignature::Make( + {InputType::Array(ty), InputType::Array(boolean()), InputType::Scalar(ty)}, ty); + kernel.exec = GenerateTypeAgnosticVarBinaryBase(*ty); + DCHECK_OK(func->AddKernel(kernel)); + } +} + +const FunctionDoc replace_doc{ + "Replace selected elements", + ("`replacement` must be a scalar of the same type as `values`.\n" + "Each unmasked value in `values` is emitted as-is.\n" + "Each masked value in `values` is replaced with `replacement`."), + {"values", "mask", "replacement"}}; + +} // namespace + +void RegisterScalarReplace(FunctionRegistry* registry) { + ScalarKernel replace_base; + replace_base.null_handling = NullHandling::COMPUTED_NO_PREALLOCATE; + replace_base.mem_allocation = MemAllocation::NO_PREALLOCATE; + auto replace = + std::make_shared("replace", Arity::Ternary(), &replace_doc); + AddBasicReplaceKernels(replace_base, replace.get()); + AddBinaryReplaceKernels(replace_base, replace.get()); + DCHECK_OK(registry->AddFunction(replace)); +} + +} // namespace internal +} // namespace compute +} // namespace arrow diff --git a/cpp/src/arrow/compute/kernels/scalar_replace_test.cc b/cpp/src/arrow/compute/kernels/scalar_replace_test.cc new file mode 100644 index 00000000000..65647e67846 --- /dev/null +++ b/cpp/src/arrow/compute/kernels/scalar_replace_test.cc @@ -0,0 +1,223 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include + +#include "arrow/compute/api.h" +#include "arrow/scalar.h" +#include "arrow/testing/gtest_util.h" +#include "arrow/testing/random.h" +#include "arrow/type.h" +#include "arrow/type_traits.h" +#include "arrow/util/bitmap_ops.h" + +namespace arrow { + +using internal::InvertBitmap; + +namespace compute { + +void CheckReplace(const Array& input, const Array& mask, const Datum& replacement, + const Array& expected, const bool ensure_no_nulls = false) { + auto Check = [&](const Array& input, const Array& mask, const Array& expected) { + ASSERT_OK_AND_ASSIGN(Datum datum_out, Replace(input, mask, replacement)); + std::shared_ptr result = datum_out.make_array(); + ASSERT_OK(result->ValidateFull()); + AssertArraysEqual(expected, *result, /*verbose=*/true); + if (ensure_no_nulls) { + if (result->null_count() != 0 || result->data()->buffers[0] != nullptr) + FAIL() << "Result shall have null_count == 0 and validity bitmap == nullptr!"; + } + }; + + Check(input, mask, expected); + + if (input.length() > 0) { + Check(*input.Slice(1), *mask.Slice(1), *expected.Slice(1)); + } +} + +void CheckReplace(const std::shared_ptr& type, const std::string& in_values, + const std::string& in_mask, const Datum& replacement, + const std::string& out_values, const bool ensure_no_nulls = false) { + std::shared_ptr input = ArrayFromJSON(type, in_values); + std::shared_ptr mask = ArrayFromJSON(boolean(), in_mask); + std::shared_ptr expected = ArrayFromJSON(type, out_values); + CheckReplace(*input, *mask, replacement, *expected, ensure_no_nulls); +} + +class TestReplaceKernel : public ::testing::Test {}; + +template +class TestReplacePrimitive : public ::testing::Test {}; + +typedef ::testing::Types + PrimitiveTypes; + +TEST_F(TestReplaceKernel, ReplaceInvalidScalar) { + auto scalar = std::make_shared(3); + scalar->is_valid = false; + CheckReplace(int8(), "[2, 4, 7, 9]", "[true, false, false, false]", Datum(scalar), + "[2, 4, 7, 9]"); +} + +TYPED_TEST_SUITE(TestReplacePrimitive, PrimitiveTypes); + +TYPED_TEST(TestReplacePrimitive, Replace) { + using T = typename TypeParam::c_type; + using ArrayType = typename TypeTraits::ArrayType; + using ScalarType = typename TypeTraits::ScalarType; + auto type = TypeTraits::type_singleton(); + auto scalar = std::make_shared(static_cast(42)); + + // No replacement + CheckReplace(type, "[2, 4, 7, 9, null]", "[false, false, false, false, false]", + Datum(scalar), "[2, 4, 7, 9, null]"); + // Some replacements + CheckReplace(type, "[2, 4, 7, 9, null]", "[true, false, true, false, true]", + Datum(scalar), "[42, 4, 42, 9, 42]", true); + // Empty Array + CheckReplace(type, "[]", "[]", Datum(scalar), "[]"); + + random::RandomArrayGenerator rand(/*seed=*/0); + auto arr = std::static_pointer_cast( + rand.ArrayOf(type, 1000, /*null_probability=*/0.01)); + // use arr inverted null bits as mask, so expect to replace all null values... + auto mask_data = std::make_shared(boolean(), arr->length(), 0); + mask_data->null_count = 0; + mask_data->buffers.resize(2); + mask_data->buffers[0] = nullptr; + mask_data->buffers[1] = *AllocateEmptyBitmap(arr->length()); + InvertBitmap(arr->data()->buffers[0]->data(), arr->offset(), arr->length(), + mask_data->buffers[1]->mutable_data(), mask_data->offset); + std::shared_ptr expected_data = arr->data()->Copy(); + expected_data->null_count = 0; + expected_data->buffers[0] = nullptr; + expected_data->buffers[1] = *AllocateBuffer(arr->length() * sizeof(T)); + T* out_data = expected_data->GetMutableValues(1); + for (int64_t i = 0; i < arr->length(); ++i) { + if (arr->IsValid(i)) { + out_data[i] = arr->Value(i); + } else { + out_data[i] = scalar->value; + } + } + CheckReplace(*arr, BooleanArray(mask_data), Datum(scalar), ArrayType(expected_data), + true); +} + +TEST_F(TestReplaceKernel, ReplaceNull) { + auto null_scalar = Datum(MakeNullScalar(boolean())); + auto true_scalar = Datum(MakeScalar(true)); + // Replace with invalid null value + CheckReplace(boolean(), "[null, null, null, null]", "[true, true, true, true]", + /*replacement=*/null_scalar, "[null, null, null, null]"); + // No replacement + CheckReplace(boolean(), "[null, null, null, null]", "[false, false, false, false]", + /*replacement=*/true_scalar, "[null, null, null, null]"); + // Some replacements + CheckReplace(boolean(), "[null, null, null, null]", "[true, false, true, false]", + /*replacement=*/true_scalar, "[true, null, true, null]"); + // Some replacements with some nulls in mask + CheckReplace(boolean(), "[null, null, null, null]", "[true, null, true, false]", + /*replacement=*/true_scalar, "[true, null, true, null]"); + // Replace all + CheckReplace(boolean(), "[null, null, null, null]", "[true, true, true, true]", + /*replacement=*/true_scalar, "[true, true, true, true]", true); +} + +TEST_F(TestReplaceKernel, ReplaceBoolean) { + auto scalar1 = std::make_shared(false); + auto scalar2 = std::make_shared(true); + + // No replacement + CheckReplace(boolean(), "[true, false, true, false]", "[false, false, false, false]", + Datum(scalar1), "[true, false, true, false]"); + // Some replacements + CheckReplace(boolean(), "[true, false, true, false]", "[true, false, true, false]", + Datum(scalar1), "[false, false, false, false]"); + // Some replacements with nulls in input + CheckReplace(boolean(), "[true, null, true, null]", "[true, false, true, false]", + Datum(scalar1), "[false, null, false, null]"); + // Some replacements with nulls in mask + CheckReplace(boolean(), "[true, false, true, null]", "[true, null, null, false]", + Datum(scalar1), "[false, false, true, null]"); + // Replace all + CheckReplace(boolean(), "[true, false, true, null]", "[true, true, true, true]", + Datum(scalar1), "[false, false, false, false]", true); + + random::RandomArrayGenerator rand(/*seed=*/0); + auto arr = + std::static_pointer_cast(rand.Boolean(1000, + /*true_probability=*/0.5, + /*null_probability=*/0.01)); + // use arr inverted null bits as mask, so expect to replace all null values... + auto mask_data = std::make_shared(boolean(), arr->length(), 0); + mask_data->null_count = 0; + mask_data->buffers.resize(2); + mask_data->buffers[0] = nullptr; + mask_data->buffers[1] = *AllocateEmptyBitmap(arr->length()); + InvertBitmap(arr->data()->buffers[0]->data(), arr->offset(), arr->length(), + mask_data->buffers[1]->mutable_data(), mask_data->offset); + auto expected_data = arr->data()->Copy(); + expected_data->null_count = 0; + expected_data->buffers[0] = nullptr; + expected_data->buffers[1] = *AllocateEmptyBitmap(arr->length()); + uint8_t* out_data = expected_data->buffers[1]->mutable_data(); + for (int64_t i = 0; i < arr->length(); ++i) { + if (arr->IsValid(i)) { + BitUtil::SetBitTo(out_data, i, arr->Value(i)); + } else { + BitUtil::SetBitTo(out_data, i, scalar1->value); + } + } + CheckReplace(*arr, BooleanArray(mask_data), Datum(scalar1), BooleanArray(expected_data), + true); +} + +TEST_F(TestReplaceKernel, ReplaceTimestamp) { + auto time32_type = time32(TimeUnit::SECOND); + auto time64_type = time64(TimeUnit::NANO); + auto scalar1 = std::make_shared(5, time32_type); + auto scalar2 = std::make_shared(6, time64_type); + // No replacement + CheckReplace(time32_type, "[2, 1, 6, null]", "[false, false, false, false]", + Datum(scalar1), "[2, 1, 6, null]"); + CheckReplace(time64_type, "[2, 1, 6, null]", "[false, false, false, false]", + Datum(scalar2), "[2, 1, 6, null]"); + // Some replacements + CheckReplace(time32_type, "[2, 1, null, 9]", "[true, false, true, false]", + Datum(scalar1), "[5, 1, 5, 9]", true); + CheckReplace(time64_type, "[2, 1, 6, null]", "[false, true, false, true]", + Datum(scalar2), "[2, 6, 6, 6]", true); +} + +TEST_F(TestReplaceKernel, ReplaceString) { + auto type = large_utf8(); + auto scalar = std::make_shared("arrow"); + // No replacement + CheckReplace(type, R"(["foo", "bar", null])", "[false, false, false]", Datum(scalar), + R"(["foo", "bar", null])"); + // Some replacements + CheckReplace(type, R"(["foo", "bar", null, null])", "[true, false, true, false]", + Datum(scalar), R"(["arrow", "bar", "arrow", null])"); +} + +} // namespace compute +} // namespace arrow diff --git a/cpp/src/arrow/compute/registry.cc b/cpp/src/arrow/compute/registry.cc index b1e0d48ccdc..bb9d0ce0e37 100644 --- a/cpp/src/arrow/compute/registry.cc +++ b/cpp/src/arrow/compute/registry.cc @@ -125,6 +125,7 @@ static std::unique_ptr CreateBuiltInRegistry() { RegisterScalarStringAscii(registry.get()); RegisterScalarValidity(registry.get()); RegisterScalarFillNull(registry.get()); + RegisterScalarReplace(registry.get()); // Aggregate functions RegisterScalarAggregateBasic(registry.get()); diff --git a/cpp/src/arrow/compute/registry_internal.h b/cpp/src/arrow/compute/registry_internal.h index 4e39eeb8204..ca85624e060 100644 --- a/cpp/src/arrow/compute/registry_internal.h +++ b/cpp/src/arrow/compute/registry_internal.h @@ -34,6 +34,7 @@ void RegisterScalarSetLookup(FunctionRegistry* registry); void RegisterScalarStringAscii(FunctionRegistry* registry); void RegisterScalarValidity(FunctionRegistry* registry); void RegisterScalarFillNull(FunctionRegistry* registry); +void RegisterScalarReplace(FunctionRegistry* registry); // Vector functions void RegisterVectorHash(FunctionRegistry* registry);