diff --git a/src/iceberg/expression/json_serde.cc b/src/iceberg/expression/json_serde.cc index 0fd7dd01d..3bcbf274f 100644 --- a/src/iceberg/expression/json_serde.cc +++ b/src/iceberg/expression/json_serde.cc @@ -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"; @@ -123,6 +129,41 @@ nlohmann::json ToJson(Expression::Operation op) { return json; } +nlohmann::json ToJson(const NamedReference& ref) { return ref.name(); } + +Result> 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())); + return std::shared_ptr(std::move(ref)); +} + +nlohmann::json ToJson(const UnboundTransform& transform) { + auto& mutable_transform = const_cast(transform); + nlohmann::json json; + json[kType] = std::string(kTransform); + json[kTransform] = transform.transform()->ToString(); + json[kTerm] = mutable_transform.reference()->name(); + return json; +} + +Result> 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(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(std::move(result)); + } + return JsonParseError("Invalid unbound transform json: {}", SafeDumpJson(json)); +} + Result> ExpressionFromJson(const nlohmann::json& json) { // Handle boolean if (json.is_boolean()) { diff --git a/src/iceberg/expression/json_serde_internal.h b/src/iceberg/expression/json_serde_internal.h index e44234d39..c0cee0fd4 100644 --- a/src/iceberg/expression/json_serde_internal.h +++ b/src/iceberg/expression/json_serde_internal.h @@ -57,6 +57,32 @@ ICEBERG_EXPORT Result> 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> 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> UnboundTransformFromJson( + const nlohmann::json& json); + /// Check if an operation is a unary predicate ICEBERG_EXPORT bool IsUnaryOperation(Expression::Operation op); diff --git a/src/iceberg/test/expression_json_test.cc b/src/iceberg/test/expression_json_test.cc index dd3ac5e3e..16c52093c 100644 --- a/src/iceberg/test/expression_json_test.cc +++ b/src/iceberg/test/expression_json_test.cc @@ -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 { @@ -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(), "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 diff --git a/src/iceberg/type_fwd.h b/src/iceberg/type_fwd.h index e97de0ac5..dc5f76c15 100644 --- a/src/iceberg/type_fwd.h +++ b/src/iceberg/type_fwd.h @@ -131,6 +131,9 @@ class Expression; class Literal; class Term; class UnboundPredicate; +class NamedReference; +class UnboundTransform; +class Transform; /// \brief Evaluator. class Evaluator;