Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cpp/src/arrow/compute/kernels/scalar_cast_internal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ Status OutputAllNull(KernelContext* ctx, const ExecSpan& batch, ExecResult* out)
// should clean this up later. This is used in the dict<null>->null cast
ArrayData* output = out->array_data().get();
output->buffers = {nullptr};
output->null_count = batch.length;
output->null_count = batch[0].array.length;
return Status::OK();
}

Expand All @@ -234,7 +234,8 @@ Status CastFromExtension(KernelContext* ctx, const ExecSpan& batch, ExecResult*
Status CastFromNull(KernelContext* ctx, const ExecSpan& batch, ExecResult* out) {
// TODO(wesm): handle this case more gracefully
std::shared_ptr<Array> nulls;
RETURN_NOT_OK(MakeArrayOfNull(out->type()->GetSharedPtr(), batch.length).Value(&nulls));
RETURN_NOT_OK(
MakeArrayOfNull(out->type()->GetSharedPtr(), batch[0].array.length).Value(&nulls));
out->value = nulls->data();
return Status::OK();
}
Expand Down
28 changes: 28 additions & 0 deletions cpp/src/arrow/compute/kernels/scalar_cast_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4309,6 +4309,34 @@ TEST(Cast, IdentityCasts) {
CheckIdentityCast(dictionary(int8(), int8()), "[1, 2, 3, 1, null, 3]");
}

TEST(Cast, NullChildLength) {
const auto struct_type = arrow::struct_({std::make_shared<Field>("a", int64(), false),
std::make_shared<Field>("n", null())});

auto struct_array = ArrayFromJSON(
struct_type, R"([{"a": 1, "n": null}, {"a": 2, "n": null}])");
ASSERT_OK_AND_ASSIGN(auto sliced_struct, Cast(*struct_array->Slice(1, 1), struct_type));
ASSERT_OK(sliced_struct->ValidateFull());

const auto output_list_type = list(struct_type);
for (const auto& source_list_type : {list(struct_type), large_list(struct_type),
list_view(struct_type), large_list_view(struct_type)}) {
auto list_array = ArrayFromJSON(
source_list_type, R"([[{"a": 1, "n": null}, {"a": 2, "n": null}]])");
ASSERT_OK_AND_ASSIGN(auto cast, Cast(*list_array, output_list_type));
ASSERT_OK(cast->ValidateFull());
}

const auto nested_type = arrow::struct_({
std::make_shared<Field>("a", arrow::struct_({std::make_shared<Field>("n", null())}),
false),
std::make_shared<Field>("b", int64(), false)});
auto nested_array =
ArrayFromJSON(nested_type, R"([{"a": {"n": null}, "b": 1}, null])");
ASSERT_OK_AND_ASSIGN(auto nested_cast, Cast(*nested_array, nested_type));
ASSERT_OK(nested_cast->ValidateFull());
}

TEST(Cast, EmptyCasts) {
// ARROW-4766: 0-length arrays should not segfault
auto CheckCastEmpty = [](std::shared_ptr<DataType> from, std::shared_ptr<DataType> to) {
Expand Down
17 changes: 17 additions & 0 deletions python/pyarrow/tests/parquet/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ def test_parquet_invalid_version(tempdir):
data_page_version="2.2")


def test_cast_struct_with_null_child_from_parquet(tempdir):
struct_type = pa.struct([
pa.field('a', pa.int64(), nullable=False),
pa.field('n', pa.null()),
])
array = pa.array([None, [{'a': 1, 'n': None}, {'a': 2, 'n': None}]],
type=pa.list_(struct_type))
path = tempdir / 'struct_null_child.parquet'
_write_table(pa.table({'x': array}), path, row_group_size=1)

schema = pq.read_schema(path)
parquet_file = pq.ParquetFile(path)
for batch in parquet_file.iter_batches(batch_size=1):
casted = batch.cast(schema)
assert casted.column(0).validate(full=True) is None


def test_set_data_page_size():
arr = pa.array([1, 2, 3] * 100000)
t = pa.Table.from_arrays([arr], names=['f0'])
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 @@ -2255,6 +2255,34 @@ def test_cast():
assert pc.cast(arr, expected.type) == expected


def test_cast_struct_with_null_child():
struct_type = pa.struct([
pa.field('a', pa.int64(), nullable=False),
pa.field('n', pa.null()),
])
array = pa.array([[{'a': 1, 'n': None}, {'a': 2, 'n': None}]],
type=pa.list_(struct_type))
struct_array = array.values

sliced_struct = struct_array.slice(1, 1)
assert sliced_struct.cast(struct_type).validate(full=True) is None

for casted in [struct_array.cast(struct_type),
pc.cast(struct_array, struct_type)]:
assert casted.validate(full=True) is None

casted = array.cast(array.type)
assert casted.validate(full=True) is None

chunked = pa.chunked_array([array]).cast(array.type)
for chunk in chunked.iterchunks():
assert chunk.validate(full=True) is None

record_batch = pa.RecordBatch.from_arrays([array], ['x'])
casted = record_batch.cast(pa.schema([pa.field('x', array.type)]))
assert casted.column(0).validate(full=True) is None


@pytest.mark.parametrize('value_type', [pa.date32(), pa.date64()])
def test_identity_cast_dates(value_type):
dt = datetime.date(1990, 3, 1)
Expand Down
Loading