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
41 changes: 41 additions & 0 deletions src/iceberg/expression/json_serde.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@

#include "iceberg/expression/json_serde_internal.h"
#include "iceberg/expression/literal.h"
#include "iceberg/expression/term.h"
#include "iceberg/transform.h"
#include "iceberg/util/checked_cast.h"
#include "iceberg/util/json_util_internal.h"
#include "iceberg/util/macros.h"

namespace iceberg {
namespace {
// JSON field names
constexpr std::string_view kType = "type";
constexpr std::string_view kTerm = "term";
constexpr std::string_view kTransform = "transform";
// Expression type strings
constexpr std::string_view kTypeTrue = "true";
constexpr std::string_view kTypeFalse = "false";
Expand Down Expand Up @@ -123,6 +129,41 @@ nlohmann::json ToJson(Expression::Operation op) {
return json;
}

nlohmann::json ToJson(const NamedReference& ref) { return ref.name(); }

Result<std::shared_ptr<NamedReference>> NamedReferenceFromJson(
const nlohmann::json& json) {
if (!json.is_string()) [[unlikely]] {
return JsonParseError("Expected string for named reference");
}
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReference::Make(json.get<std::string>()));
return std::shared_ptr<NamedReference>(std::move(ref));
}

nlohmann::json ToJson(const UnboundTransform& transform) {
auto& mutable_transform = const_cast<UnboundTransform&>(transform);
nlohmann::json json;
json[kType] = std::string(kTransform);
json[kTransform] = transform.transform()->ToString();
json[kTerm] = mutable_transform.reference()->name();
return json;
}

Result<std::shared_ptr<UnboundTransform>> UnboundTransformFromJson(
const nlohmann::json& json) {
if (json.is_object() && json.contains(kType) &&
json[kType] == std::string(kTransform) && json.contains(kTerm)) {
ICEBERG_ASSIGN_OR_RAISE(auto transform_str,
GetJsonValue<std::string>(json, kTransform));
ICEBERG_ASSIGN_OR_RAISE(auto transform, TransformFromString(transform_str));
ICEBERG_ASSIGN_OR_RAISE(auto ref, NamedReferenceFromJson(json[kTerm]));
ICEBERG_ASSIGN_OR_RAISE(auto result,
UnboundTransform::Make(std::move(ref), std::move(transform)));
return std::shared_ptr<UnboundTransform>(std::move(result));
}
return JsonParseError("Invalid unbound transform json: {}", SafeDumpJson(json));
}

Result<std::shared_ptr<Expression>> ExpressionFromJson(const nlohmann::json& json) {
// Handle boolean
if (json.is_boolean()) {
Expand Down
26 changes: 26 additions & 0 deletions src/iceberg/expression/json_serde_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,32 @@ ICEBERG_EXPORT Result<std::shared_ptr<Expression>> ExpressionFromJson(
/// \return A JSON object representing the expression
ICEBERG_EXPORT nlohmann::json ToJson(const Expression& expr);

/// \brief Deserializes a JSON object into a NamedReference.
///
/// \param json A JSON object representing a named reference
/// \return A shared pointer to the deserialized NamedReference or an error
ICEBERG_EXPORT Result<std::shared_ptr<NamedReference>> NamedReferenceFromJson(
const nlohmann::json& json);

/// \brief Serializes a NamedReference into its JSON representation.
///
/// \param ref The named reference to serialize
/// \return A JSON object representing the named reference
ICEBERG_EXPORT nlohmann::json ToJson(const NamedReference& ref);

/// \brief Serializes an UnboundTransform into its JSON representation.
///
/// \param transform The unbound transform to serialize
/// \return A JSON object representing the unbound transform
ICEBERG_EXPORT nlohmann::json ToJson(const UnboundTransform& transform);

/// \brief Deserializes a JSON object into an UnboundTransform.
///
/// \param json A JSON object representing an unbound transform
/// \return A shared pointer to the deserialized UnboundTransform or an error
ICEBERG_EXPORT Result<std::shared_ptr<UnboundTransform>> UnboundTransformFromJson(
const nlohmann::json& json);

/// Check if an operation is a unary predicate
ICEBERG_EXPORT bool IsUnaryOperation(Expression::Operation op);

Expand Down
50 changes: 50 additions & 0 deletions src/iceberg/test/expression_json_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "iceberg/expression/predicate.h"
#include "iceberg/expression/term.h"
#include "iceberg/test/matchers.h"
#include "iceberg/transform.h"

namespace iceberg {

Expand Down Expand Up @@ -63,4 +64,53 @@ TEST(ExpressionJsonTest, OperationTypeTests) {
EXPECT_FALSE(IsUnaryOperation(Expression::Operation::kTrue));
}

TEST(ExpressionJsonTest, NameReferenceRoundTrip) {
ICEBERG_UNWRAP_OR_FAIL(auto ref, NamedReference::Make("col_name"));
auto json = ToJson(*ref);
EXPECT_EQ(json.get<std::string>(), "col_name");

ICEBERG_UNWRAP_OR_FAIL(auto parsed, NamedReferenceFromJson(json));
EXPECT_EQ(parsed->name(), "col_name");
}

TEST(ExpressionJsonTest, UnboundTransfromRoundTrip) {
ICEBERG_UNWRAP_OR_FAIL(auto ref, NamedReference::Make("ts"));
auto transform = Transform::Day();
ICEBERG_UNWRAP_OR_FAIL(auto unbound, UnboundTransform::Make(std::move(ref), transform));

auto json = ToJson(*unbound);
EXPECT_EQ(json["type"], "transform");
EXPECT_EQ(json["transform"], "day");
EXPECT_EQ(json["term"], "ts");

ICEBERG_UNWRAP_OR_FAIL(auto parsed, UnboundTransformFromJson(json));
EXPECT_EQ(parsed->reference()->name(), unbound->reference()->name());
EXPECT_EQ(parsed->transform()->transform_type(),
unbound->transform()->transform_type());
EXPECT_EQ(parsed->transform()->ToString(), unbound->transform()->ToString());
}

TEST(ExpressionJsonTest, BucketTransform) {
ICEBERG_UNWRAP_OR_FAIL(auto ref, NamedReference::Make("id"));
ICEBERG_UNWRAP_OR_FAIL(auto unbound,
UnboundTransform::Make(std::move(ref), Transform::Bucket(16)));

auto json = ToJson(*unbound);
EXPECT_EQ(json["type"], "transform");
EXPECT_EQ(json["transform"], "bucket[16]");
EXPECT_EQ(json["term"], "id");

ICEBERG_UNWRAP_OR_FAIL(auto parsed, UnboundTransformFromJson(json));
EXPECT_EQ(parsed->transform()->transform_type(),
unbound->transform()->transform_type());
EXPECT_EQ(parsed->transform()->ToString(), unbound->transform()->ToString());
}

TEST(ExpressionJsonTest, InvalidInput) {
EXPECT_THAT(UnboundTransformFromJson(nlohmann::json::object()),
IsError(ErrorKind::kJsonParseError));
EXPECT_THAT(UnboundTransformFromJson(nlohmann::json{{"type", "other"}}),
IsError(ErrorKind::kJsonParseError));
}

} // namespace iceberg
3 changes: 3 additions & 0 deletions src/iceberg/type_fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ class Expression;
class Literal;
class Term;
class UnboundPredicate;
class NamedReference;
class UnboundTransform;
class Transform;

/// \brief Evaluator.
class Evaluator;
Expand Down
Loading