Skip to content
Closed
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
24 changes: 20 additions & 4 deletions cpp/src/arrow/compute/kernels/scalar_nested.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,15 @@ const FunctionDoc list_value_length_doc{
Result<ValueDescr> ProjectResolve(KernelContext* ctx,
const std::vector<ValueDescr>& descrs) {
const auto& names = OptionsWrapper<ProjectOptions>::Get(ctx).field_names;
if (names.size() != descrs.size()) {
return Status::Invalid("project() was passed ", names.size(), " field ", "names but ",
descrs.size(), " arguments");
const auto& nullable = OptionsWrapper<ProjectOptions>::Get(ctx).field_nullability;
const auto& metadata = OptionsWrapper<ProjectOptions>::Get(ctx).field_metadata;

if (names.size() != descrs.size() || nullable.size() != descrs.size() ||
metadata.size() != descrs.size()) {
return Status::Invalid("project() was passed ", descrs.size(), " arguments but ",
names.size(), " field names, ", nullable.size(),
" nullability bits, and ", metadata.size(),
" metadata dictionaries.");
}

size_t i = 0;
Expand All @@ -86,7 +92,7 @@ Result<ValueDescr> ProjectResolve(KernelContext* ctx,
}
}

fields[i] = field(names[i], descr.type);
fields[i] = field(names[i], descr.type, nullable[i], metadata[i]);
++i;
}

Expand All @@ -96,6 +102,16 @@ Result<ValueDescr> ProjectResolve(KernelContext* ctx,
void ProjectExec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
KERNEL_ASSIGN_OR_RAISE(auto descr, ctx, ProjectResolve(ctx, batch.GetDescriptors()));

for (int i = 0; i < batch.num_values(); ++i) {
const auto& field = checked_cast<const StructType&>(*descr.type).field(i);
if (batch[i].null_count() > 0 && !field->nullable()) {
ctx->SetStatus(Status::Invalid("Output field ", field, " (#", i,
") does not allow nulls but the corresponding "
"argument was not entirely valid."));
return;
}
}

if (descr.shape == ValueDescr::SCALAR) {
ScalarVector scalars(batch.num_values());
for (int i = 0; i < batch.num_values(); ++i) {
Expand Down
88 changes: 53 additions & 35 deletions cpp/src/arrow/compute/kernels/scalar_nested_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "arrow/compute/kernels/test_util.h"
#include "arrow/result.h"
#include "arrow/testing/gtest_util.h"
#include "arrow/util/key_value_metadata.h"

namespace arrow {
namespace compute {
Expand All @@ -38,51 +39,71 @@ TEST(TestScalarNested, ListValueLength) {
}

struct {
public:
Result<Datum> operator()(std::vector<Datum> args) {
ProjectOptions opts{field_names};
template <typename... Options>
Result<Datum> operator()(std::vector<Datum> args, std::vector<std::string> field_names,
Options... options) {
ProjectOptions opts{field_names, options...};
return CallFunction("project", args, &opts);
}

std::vector<std::string> field_names;
} Project;

TEST(Project, Scalar) {
std::shared_ptr<StructScalar> expected(new StructScalar{{}, struct_({})});
ASSERT_OK_AND_EQ(Datum(expected), Project({}));

auto i32 = MakeScalar(1);
auto f64 = MakeScalar(2.5);
auto str = MakeScalar("yo");

expected.reset(new StructScalar{
{i32, f64, str},
struct_({field("i", i32->type), field("f", f64->type), field("s", str->type)})});
Project.field_names = {"i", "f", "s"};
ASSERT_OK_AND_EQ(Datum(expected), Project({i32, f64, str}));
ASSERT_OK_AND_ASSIGN(auto expected,
StructScalar::Make({i32, f64, str}, {"i", "f", "s"}));
ASSERT_OK_AND_EQ(Datum(expected), Project({i32, f64, str}, {"i", "f", "s"}));

// Three field names but one input value
ASSERT_RAISES(Invalid, Project({str}));
ASSERT_RAISES(Invalid, Project({str}, {"i", "f", "s"}));

// No field names or input values is fine
expected.reset(new StructScalar{{}, struct_({})});
ASSERT_OK_AND_EQ(Datum(expected), Project(/*args=*/{}, /*field_names=*/{}));
}

TEST(Project, Array) {
Project.field_names = {"i", "s"};
std::vector<std::string> field_names{"i", "s"};

auto i32 = ArrayFromJSON(int32(), "[42, 13, 7]");
auto str = ArrayFromJSON(utf8(), R"(["aa", "aa", "aa"])");
ASSERT_OK_AND_ASSIGN(Datum expected,
StructArray::Make({i32, str}, Project.field_names));
ASSERT_OK_AND_ASSIGN(Datum expected, StructArray::Make({i32, str}, field_names));

ASSERT_OK_AND_EQ(expected, Project({i32, str}));
ASSERT_OK_AND_EQ(expected, Project({i32, str}, field_names));

// Scalars are broadcast to the length of the arrays
ASSERT_OK_AND_EQ(expected, Project({i32, MakeScalar("aa")}));
ASSERT_OK_AND_EQ(expected, Project({i32, MakeScalar("aa")}, field_names));

// Array length mismatch
ASSERT_RAISES(Invalid, Project({i32->Slice(1), str}));
ASSERT_RAISES(Invalid, Project({i32->Slice(1), str}, field_names));
}

TEST(Project, NullableMetadataPassedThru) {
auto i32 = ArrayFromJSON(int32(), "[42, 13, 7]");
auto str = ArrayFromJSON(utf8(), R"(["aa", "aa", "aa"])");

std::vector<std::string> field_names{"i", "s"};
std::vector<bool> nullability{true, false};
std::vector<std::shared_ptr<const KeyValueMetadata>> metadata = {
key_value_metadata({"a", "b"}, {"ALPHA", "BRAVO"}), nullptr};

ASSERT_OK_AND_ASSIGN(auto proj,
Project({i32, str}, field_names, nullability, metadata));

AssertTypeEqual(*proj.type(), StructType({
field("i", int32(), /*nullable=*/true, metadata[0]),
field("s", utf8(), /*nullable=*/false, nullptr),
}));
Comment thread
bkietz marked this conversation as resolved.
Outdated

// error: projecting an array containing nulls with nullable=false
str = ArrayFromJSON(utf8(), R"(["aa", null, "aa"])");
ASSERT_RAISES(Invalid, Project({i32, str}, field_names, nullability, metadata));
}

TEST(Project, ChunkedArray) {
Project.field_names = {"i", "s"};
std::vector<std::string> field_names{"i", "s"};

auto i32_0 = ArrayFromJSON(int32(), "[42, 13, 7]");
auto i32_1 = ArrayFromJSON(int32(), "[]");
Expand All @@ -95,26 +116,23 @@ TEST(Project, ChunkedArray) {
ASSERT_OK_AND_ASSIGN(auto i32, ChunkedArray::Make({i32_0, i32_1, i32_2}));
ASSERT_OK_AND_ASSIGN(auto str, ChunkedArray::Make({str_0, str_1, str_2}));

ASSERT_OK_AND_ASSIGN(auto expected_0,
StructArray::Make({i32_0, str_0}, Project.field_names));
ASSERT_OK_AND_ASSIGN(auto expected_1,
StructArray::Make({i32_1, str_1}, Project.field_names));
ASSERT_OK_AND_ASSIGN(auto expected_2,
StructArray::Make({i32_2, str_2}, Project.field_names));
ASSERT_OK_AND_ASSIGN(auto expected_0, StructArray::Make({i32_0, str_0}, field_names));
ASSERT_OK_AND_ASSIGN(auto expected_1, StructArray::Make({i32_1, str_1}, field_names));
ASSERT_OK_AND_ASSIGN(auto expected_2, StructArray::Make({i32_2, str_2}, field_names));
ASSERT_OK_AND_ASSIGN(Datum expected,
ChunkedArray::Make({expected_0, expected_1, expected_2}));

ASSERT_OK_AND_EQ(expected, Project({i32, str}));
ASSERT_OK_AND_EQ(expected, Project({i32, str}, field_names));

// Scalars are broadcast to the length of the arrays
ASSERT_OK_AND_EQ(expected, Project({i32, MakeScalar("aa")}));
ASSERT_OK_AND_EQ(expected, Project({i32, MakeScalar("aa")}, field_names));

// Array length mismatch
ASSERT_RAISES(Invalid, Project({i32->Slice(1), str}));
ASSERT_RAISES(Invalid, Project({i32->Slice(1), str}, field_names));
}

TEST(Project, ChunkedArrayDifferentChunking) {
Project.field_names = {"i", "s"};
std::vector<std::string> field_names{"i", "s"};

auto i32_0 = ArrayFromJSON(int32(), "[42, 13, 7]");
auto i32_1 = ArrayFromJSON(int32(), "[]");
Expand All @@ -136,18 +154,18 @@ TEST(Project, ChunkedArrayDifferentChunking) {
for (size_t i = 0; i < expected_chunks.size(); ++i) {
ASSERT_OK_AND_ASSIGN(expected_chunks[i], StructArray::Make({expected_rechunked[0][i],
expected_rechunked[1][i]},
Project.field_names));
field_names));
}

ASSERT_OK_AND_ASSIGN(Datum expected, ChunkedArray::Make(expected_chunks));

ASSERT_OK_AND_EQ(expected, Project({i32, str}));
ASSERT_OK_AND_EQ(expected, Project({i32, str}, field_names));

// Scalars are broadcast to the length of the arrays
ASSERT_OK_AND_EQ(expected, Project({i32, MakeScalar("aa")}));
ASSERT_OK_AND_EQ(expected, Project({i32, MakeScalar("aa")}, field_names));

// Array length mismatch
ASSERT_RAISES(Invalid, Project({i32->Slice(1), str}));
ASSERT_RAISES(Invalid, Project({i32->Slice(1), str}, field_names));
}

} // namespace compute
Expand Down
Loading