Skip to content
Merged
8 changes: 4 additions & 4 deletions cpp/src/arrow/compute/kernels/vector_replace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,15 @@ struct ReplaceMaskImpl<Type, enable_if_null<Type>> {
static Result<int64_t> ExecScalarMask(KernelContext* ctx, const ArraySpan& array,
const BooleanScalar& mask, ExecValue replacements,
int64_t replacements_offset, ExecResult* out) {
out->value = array;
return Status::OK();
out->value = array.ToArrayData();
return replacements_offset;
}
static Result<int64_t> ExecArrayMask(KernelContext* ctx, const ArraySpan& array,
const ArraySpan& mask, int64_t mask_offset,
ExecValue replacements,
int64_t replacements_offset, ExecResult* out) {
out->value = array;
return Status::OK();
out->value = array.ToArrayData();
return replacements_offset;
}
};

Expand Down
36 changes: 36 additions & 0 deletions cpp/src/arrow/compute/kernels/vector_replace_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ class TestReplaceBoolean : public TestReplaceKernel<BooleanType> {
}
};

class TestReplaceNull : public TestReplaceKernel<NullType> {
protected:
std::shared_ptr<DataType> type() override {
return TypeTraits<NullType>::type_singleton();
}
};

class TestReplaceFixedSizeBinary : public TestReplaceKernel<FixedSizeBinaryType> {
protected:
std::shared_ptr<DataType> type() override { return fixed_size_binary(3); }
Expand Down Expand Up @@ -538,6 +545,35 @@ TEST_F(TestReplaceBoolean, ReplaceWithMask) {
}
}

TEST_F(TestReplaceNull, ReplaceWithMask) {
std::vector<ReplaceWithMaskCase> cases = {
{this->array("[]"), this->mask_scalar(false), this->array("[]"), this->array("[]")},
{this->array("[]"), this->mask_scalar(true), this->array("[]"), this->array("[]")},
{this->array("[]"), this->null_mask_scalar(), this->array("[]"), this->array("[]")},

{this->array("[null]"), this->mask_scalar(false), this->array("[]"),
this->array("[null]")},

{this->array("[null]"), this->mask_scalar(true), this->array("[null]"),
this->array("[null]")},

{this->array("[null]"), this->null_mask_scalar(), this->array("[]"),
this->array("[null]")},

{this->array("[null, null]"), this->mask("[false, false]"), this->array("[]"),
this->array("[null, null]")},
{this->array("[null, null]"), this->mask("[true, true]"),
this->array("[null, null]"), this->array("[null, null]")},
{this->array("[null, null]"), this->mask("[null, null]"), this->array("[]"),
this->array("[null, null]")},
};

for (auto test_case : cases) {
this->Assert(ReplaceWithMask, test_case.input, test_case.mask, test_case.replacements,
test_case.expected);
}
}

TEST_F(TestReplaceBoolean, ReplaceWithMaskErrors) {
EXPECT_RAISES_WITH_MESSAGE_THAT(
Invalid,
Expand Down
28 changes: 28 additions & 0 deletions python/pyarrow/tests/test_compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,34 @@ def test_extract_regex_span():
assert struct.tolist() == expected


def test_replace_with_mask_null_type():
# GH-47447: replace_with_mask crashed for null type arrays
input = pa.array([None], pa.null())
replacements = pa.array([None], pa.null())

result = pc.replace_with_mask(input, True, replacements)
assert result.type == pa.null()
result.validate(full=True)
assert result.to_pylist() == [None]

result = pc.replace_with_mask(input, False, replacements)
assert result.type == pa.null()
result.validate(full=True)
assert result.to_pylist() == [None]

mask = pa.array([True])
result = pc.replace_with_mask(input, mask, replacements)
assert result.type == pa.null()
result.validate(full=True)
assert result.to_pylist() == [None]

mask = pa.array([False])
result = pc.replace_with_mask(input, mask, replacements)
assert result.type == pa.null()
result.validate(full=True)
assert result.to_pylist() == [None]


def test_binary_join():
ar_list = pa.array([['foo', 'bar'], None, []])
expected = pa.array(['foo-bar', None, ''])
Expand Down
Loading