Skip to content
Merged
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
3 changes: 3 additions & 0 deletions lang/cpp/include/vortex/array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "vortex/dtype.hpp"
#include "vortex/error.hpp"
#include "vortex/expression.hpp"
#include "vortex/scalar.hpp"
#include "vortex/session.hpp"

#include <vortex.h>
Expand Down Expand Up @@ -211,6 +212,8 @@ class Array {
// Bulk view over Binary values.
BytesView bytes(const Session &session) const;

Scalar scalar_at(const Session &session, size_t index) const;

private:
friend struct detail::Access;
friend class StringView;
Expand Down
147 changes: 143 additions & 4 deletions lang/cpp/include/vortex/scalar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,159 @@ class Scalar {
bool is_null() const;
DataType dtype() const;

/**
* Read scalar's value.
*
* Supported types are bool, primitives, std::string_view, and BinaryView.
*
* std::string_view and BinaryView returned borrow from scalar and stay
* valid while scalar is valid.
*
* Throws if scalar type does not match T or value is Null.
Comment thread
myrrc marked this conversation as resolved.
*/
template <element_type T>
T get() const;

/**
* Read a decimal scalar's unscaled value.
* int8/16/32/64_t are supported.
* Throws if scalar is not a decimal or is Null.
* Trying to get decimal's value that overflows T is UB.
*/
template <primitive_type T>
T get_decimal() const;

private:
friend struct detail::Access;
explicit Scalar(vx_scalar *owned) : handle_(owned) {
explicit Scalar(const vx_scalar *owned) : handle_(owned) {
}
vx_scalar *release() && {
const vx_scalar *release() && {
return handle_.release();
}

struct Deleter {
void operator()(vx_scalar *ptr) const noexcept;
void operator()(const vx_scalar *ptr) const noexcept;
};
std::unique_ptr<vx_scalar, Deleter> handle_;
std::unique_ptr<const vx_scalar, Deleter> handle_;
};

template <element_type T>
T Scalar::get() const {
using enum DataTypeVariant;
using enum ErrorCode;
using enum PType;

const DataType data_type = dtype();
const DataTypeVariant variant = data_type.variant();
if (is_null()) {
throw VortexException("Scalar is null", InvalidArgument);
}

const vx_scalar *const h = handle_.get();
if constexpr (std::is_same_v<T, bool>) {
if (variant != Bool) {
throw VortexException("scalar get type doesn't match", InvalidArgument);
}
return vx_scalar_get_bool(h);
} else if constexpr (std::is_same_v<T, std::string_view>) {
if (variant != Utf8) {
throw VortexException("scalar get type doesn't match", InvalidArgument);
}
vx_view v = vx_scalar_get_utf8(h);
return std::string_view(v.ptr, v.len);
} else if constexpr (std::is_same_v<T, BinaryView>) {
if (variant != Binary) {
throw VortexException("scalar get type doesn't match", InvalidArgument);
}
vx_view v = vx_scalar_get_binary(h);
return BinaryView(reinterpret_cast<const std::byte *>(v.ptr), v.len);
} else if constexpr (std::is_same_v<T, uint8_t>) {
if (variant != Primitive || data_type.primitive_type() != U8) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_u8(h);
} else if constexpr (std::is_same_v<T, uint16_t>) {
if (variant != Primitive || data_type.primitive_type() != U16) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_u16(h);
} else if constexpr (std::is_same_v<T, uint32_t>) {
if (variant != Primitive || data_type.primitive_type() != U32) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_u32(h);
} else if constexpr (std::is_same_v<T, uint64_t>) {
if (variant != Primitive || data_type.primitive_type() != U64) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_u64(h);
} else if constexpr (std::is_same_v<T, int8_t>) {
if (variant != Primitive || data_type.primitive_type() != I8) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_i8(h);
} else if constexpr (std::is_same_v<T, int16_t>) {
if (variant != Primitive || data_type.primitive_type() != I16) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_i16(h);
} else if constexpr (std::is_same_v<T, int32_t>) {
if (variant != Primitive || data_type.primitive_type() != I32) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_i32(h);
} else if constexpr (std::is_same_v<T, int64_t>) {
if (variant != Primitive || data_type.primitive_type() != I64) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_i64(h);
} else if constexpr (std::is_same_v<T, float>) {
if (variant != Primitive || data_type.primitive_type() != F32) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_f32(h);
} else if constexpr (std::is_same_v<T, double>) {
if (variant != Primitive || data_type.primitive_type() != F64) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
return vx_scalar_get_f64(h);
} else if constexpr (std::is_same_v<T, float16_t>) {
if (variant != Primitive || data_type.primitive_type() != F16) {
throw VortexException("scalar get type doesn't match", ErrorCode::InvalidArgument);
}
const uint16_t bits = vx_scalar_get_f16_bits(h);
#if __STDCPP_FLOAT16_T__ != 1
return {.bits = bits};
#else
return std::bit_cast<float16_t>(bits);
#endif
} else {
static_assert(false, "scalar get is not supported for this type");
}
}

template <primitive_type T>
T Scalar::get_decimal() const {
if (is_null()) {
throw VortexException("Scalar is null", ErrorCode::InvalidArgument);
}
if (dtype().variant() != DataTypeVariant::Decimal) {
throw VortexException("DataType is not Decimal", ErrorCode::InvalidArgument);
}
const vx_scalar *const h = handle_.get();
if constexpr (std::is_same_v<T, int8_t>) {
return vx_scalar_get_decimal_i8(h);
} else if constexpr (std::is_same_v<T, int16_t>) {
return vx_scalar_get_decimal_i16(h);
} else if constexpr (std::is_same_v<T, int32_t>) {
return vx_scalar_get_decimal_i32(h);
} else if constexpr (std::is_same_v<T, int64_t>) {
return vx_scalar_get_decimal_i64(h);
} else {
static_assert(false, "unsupported decimal scalar get type");
}
}

namespace detail {
vx_scalar *make_bool(bool value, bool nullable);
vx_scalar *make_primitive(vx_ptype ptype, const void *value, bool nullable);
Expand Down
14 changes: 14 additions & 0 deletions lang/cpp/src/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "vortex/common.hpp"
#include "vortex/dtype.hpp"
#include "vortex/error.hpp"
#include "vortex/scalar.hpp"
#include "vortex/session.hpp"

#include <vortex.h>

Expand Down Expand Up @@ -324,7 +326,19 @@ BytesView Array::bytes(const Session &session) const {
return BytesView(std::move(canonical), std::move(validity), len);
}

Scalar Array::scalar_at(const Session &session, size_t index) const {
vx_error *error = nullptr;
const vx_scalar *scalar = vx_array_get_scalar(Access::c_ptr(session), handle_.get(), index, &error);
throw_on_error(error);
return Access::adopt<Scalar>(scalar);
}

bool PrimitiveView<bool>::value(size_t i) const {
if (i >= size_) {
throw VortexException("index " + std::to_string(i) + " out of bounds for view of size " +
std::to_string(size_),
ErrorCode::OutOfBounds);
}
return vx_array_get_bool(Access::c_ptr(canonical_), i);
}

Expand Down
2 changes: 1 addition & 1 deletion lang/cpp/src/scalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace vortex {
using detail::Access;
using detail::throw_on_error;

void Scalar::Deleter::operator()(vx_scalar *ptr) const noexcept {
void Scalar::Deleter::operator()(const vx_scalar *ptr) const noexcept {
vx_scalar_free(ptr);
}

Expand Down
1 change: 1 addition & 0 deletions lang/cpp/tests/expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ TEST_CASE("Operator overloading", "[expr]") {
REQUIRE(bits.value(1));
REQUIRE(bits.value(2));
REQUIRE_FALSE(bits.value(3));
REQUIRE_THROWS_AS(bits.value(data.size()), VortexException);
}

TEST_CASE("Apply error", "[expr]") {
Expand Down
3 changes: 2 additions & 1 deletion lang/cpp/tests/float16_t.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

#include "vortex/dtype.hpp"
#include <catch2/catch_test_macros.hpp>
#include <vortex/scalar.hpp>

Expand All @@ -25,11 +24,13 @@ TEST_CASE("F16 scalar", "[scalar]") {
Scalar scalar = scalar::of(float16t);
REQUIRE(scalar.dtype().variant() == DataTypeVariant::Primitive);
REQUIRE(scalar.dtype().primitive_type() == vortex::PType::F16);
REQUIRE(scalar.get<float16_t>() == float16t);

_Float16 float16t_alias = 1.0f16;
scalar = scalar::of(float16t_alias);
REQUIRE(scalar.dtype().variant() == DataTypeVariant::Primitive);
REQUIRE(scalar.dtype().primitive_type() == vortex::PType::F16);
REQUIRE(scalar.get<float16_t>() == float16t_alias);
}
#endif
} // namespace
1 change: 1 addition & 0 deletions lang/cpp/tests/float16_t_compat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ TEST_CASE("F16 scalar (compatibility)", "[scalar]") {
Scalar scalar = scalar::of(float16t);
REQUIRE(scalar.dtype().variant() == DataTypeVariant::Primitive);
REQUIRE(scalar.dtype().primitive_type() == vortex::PType::F16);
REQUIRE(scalar.get<float16_t>() == float16t);
}
} // namespace
42 changes: 41 additions & 1 deletion lang/cpp/tests/scalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ using namespace std::string_view_literals;

namespace {
using enum vortex::PType;
using scalar::decimal;
using scalar::of;

TEST_CASE("Boolean scalar", "[scalar]") {
Scalar s = scalar::of(true);
Expand All @@ -29,7 +31,7 @@ TEST_CASE("Integer scalars", "[scalar]") {
}

TEST_CASE("Float scalars", "[scalar]") {
REQUIRE(scalar::of(1.5F).dtype().primitive_type() == F32);
REQUIRE(scalar::of(1.5f).dtype().primitive_type() == F32);
REQUIRE(scalar::of(1.5).dtype().primitive_type() == F64);
REQUIRE(scalar::of(float16_t {0x3C00}).dtype().primitive_type() == F16);
}
Expand All @@ -44,15 +46,19 @@ TEST_CASE("Null scalar", "[scalar]") {
Scalar s = scalar::null(dtype::int32(true));
REQUIRE(s.is_null());
REQUIRE(s.dtype().variant() == DataTypeVariant::Primitive);
REQUIRE_THROWS_AS(s.get<int32_t>(), VortexException);
}

TEST_CASE("UTF-8 scalar", "[scalar]") {
Scalar s = scalar::of("hello"sv);
REQUIRE_FALSE(s.is_null());
REQUIRE(s.dtype().variant() == DataTypeVariant::Utf8);
REQUIRE_THROWS_AS(s.get<float>(), VortexException);

REQUIRE_FALSE(scalar::of(""sv).is_null());
s = scalar::of("Широкая строка"sv);
REQUIRE_THROWS_AS(s.get<bool>(), VortexException);
REQUIRE_THROWS_AS(s.get_decimal<int8_t>(), VortexException);
REQUIRE(s.dtype().variant() == DataTypeVariant::Utf8);

REQUIRE_THROWS_AS(scalar::of("\xFF\xFE"sv), VortexException);
Expand Down Expand Up @@ -87,6 +93,40 @@ TEST_CASE("Decimal scalars", "[scalar]") {
REQUIRE(d64.dtype().decimal_scale() == 3);
}

TEST_CASE("Invalid scalar getter", "[scalar]") {
Scalar scalar = of<int32_t>(0);
REQUIRE_THROWS_AS(scalar.get<double>(), VortexException);
}

TEST_CASE("Primitive getters", "[scalar]") {
REQUIRE(of<uint64_t>(1ULL << 40).get<uint64_t>() == (1ULL << 40));
REQUIRE(of<int32_t>(-7).get<int32_t>() == -7);
REQUIRE(of(2.5).get<double>() == 2.5);
REQUIRE(of(true).get<bool>());
}

TEST_CASE("String getters", "[scalar]") {
Scalar s = of("hello"sv);
REQUIRE(s.get<std::string_view>() == "hello"sv);

const std::byte bytes[] = {std::byte {1}, std::byte {2}, std::byte {0}, std::byte {4}};
Scalar b = of(std::span<const std::byte> {bytes});
BinaryView view = b.get<BinaryView>();
REQUIRE(view.size() == 4);
REQUIRE(view[0] == std::byte {1});
REQUIRE(view[3] == std::byte {4});
}

TEST_CASE("Decimal getters", "[scalar]") {
Scalar s = scalar::null(dtype::decimal(5, 2));
REQUIRE_THROWS_AS(s.get_decimal<int8_t>(), VortexException);

REQUIRE(decimal<int8_t>(56, 5, 2).get_decimal<int8_t>() == 56);
REQUIRE(decimal<int16_t>(1234, 5, 2).get_decimal<int16_t>() == 1234);
REQUIRE(decimal<int32_t>(5678, 6, 2).get_decimal<int32_t>() == 5678);
REQUIRE(decimal<int64_t>(99999, 12, 3).get_decimal<int64_t>() == 99999);
}

TEST_CASE("Copy scalar", "[scalar]") {
Scalar a = scalar::of<int64_t>(42);
Scalar b = a;
Expand Down
Loading
Loading