Skip to content

Commit a901428

Browse files
author
Innocent
committed
feat: Add JSON serialization for expression(#331)
to squash
1 parent bc2e026 commit a901428

File tree

7 files changed

+286
-0
lines changed

7 files changed

+286
-0
lines changed

src/iceberg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ set(ICEBERG_SOURCES
3131
expression/expression.cc
3232
expression/expressions.cc
3333
expression/inclusive_metrics_evaluator.cc
34+
expression/json_serde.cc
3435
expression/literal.cc
3536
expression/manifest_evaluator.cc
3637
expression/predicate.cc
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include <format>
21+
#include <ranges>
22+
#include <string>
23+
#include <utility>
24+
#include <vector>
25+
26+
#include <nlohmann/json.hpp>
27+
28+
#include "iceberg/expression/json_serde_internal.h"
29+
#include "iceberg/expression/literal.h"
30+
#include "iceberg/util/checked_cast.h"
31+
#include "iceberg/util/json_util_internal.h"
32+
#include "iceberg/util/macros.h"
33+
34+
namespace iceberg {
35+
namespace {
36+
// Expression type strings
37+
constexpr std::string_view kTypeTrue = "true";
38+
constexpr std::string_view kTypeFalse = "false";
39+
constexpr std::string_view kTypeEq = "eq";
40+
constexpr std::string_view kTypeAnd = "and";
41+
constexpr std::string_view kTypeOr = "or";
42+
constexpr std::string_view kTypeNot = "not";
43+
constexpr std::string_view kTypeIn = "in";
44+
constexpr std::string_view kTypeNotIn = "not-in";
45+
constexpr std::string_view kTypeLt = "lt";
46+
constexpr std::string_view kTypeLtEq = "lt-eq";
47+
constexpr std::string_view kTypeGt = "gt";
48+
constexpr std::string_view kTypeGtEq = "gt-eq";
49+
constexpr std::string_view kTypeNotEq = "not-eq";
50+
constexpr std::string_view kTypeStartsWith = "starts-with";
51+
constexpr std::string_view kTypeNotStartsWith = "not-starts-with";
52+
constexpr std::string_view kTypeIsNull = "is-null";
53+
constexpr std::string_view kTypeNotNull = "not-null";
54+
constexpr std::string_view kTypeIsNan = "is-nan";
55+
constexpr std::string_view kTypeNotNan = "not-nan";
56+
constexpr std::string_view kCount = "count";
57+
constexpr std::string_view kCountNull = "count-null";
58+
constexpr std::string_view kCountStar = "count-star";
59+
constexpr std::string_view kMin = "min";
60+
constexpr std::string_view kMax = "max";
61+
} // namespace
62+
63+
bool IsUnaryOperation(Expression::Operation op) {
64+
switch (op) {
65+
case Expression::Operation::kIsNull:
66+
case Expression::Operation::kNotNull:
67+
case Expression::Operation::kIsNan:
68+
case Expression::Operation::kNotNan:
69+
return true;
70+
default:
71+
return false;
72+
}
73+
}
74+
75+
bool IsSetOperation(Expression::Operation op) {
76+
switch (op) {
77+
case Expression::Operation::kIn:
78+
case Expression::Operation::kNotIn:
79+
return true;
80+
default:
81+
return false;
82+
}
83+
}
84+
85+
Result<Expression::Operation> OperationTypeFromJson(const nlohmann::json& json) {
86+
if (!json.is_string()) {
87+
return JsonParseError("Unable to create operation. Json value is not a string");
88+
}
89+
auto typeStr = json.get<std::string>();
90+
if (typeStr == kTypeTrue) return Expression::Operation::kTrue;
91+
if (typeStr == kTypeFalse) return Expression::Operation::kFalse;
92+
if (typeStr == kTypeAnd) return Expression::Operation::kAnd;
93+
if (typeStr == kTypeOr) return Expression::Operation::kOr;
94+
if (typeStr == kTypeNot) return Expression::Operation::kNot;
95+
if (typeStr == kTypeEq) return Expression::Operation::kEq;
96+
if (typeStr == kTypeNotEq) return Expression::Operation::kNotEq;
97+
if (typeStr == kTypeLt) return Expression::Operation::kLt;
98+
if (typeStr == kTypeLtEq) return Expression::Operation::kLtEq;
99+
if (typeStr == kTypeGt) return Expression::Operation::kGt;
100+
if (typeStr == kTypeGtEq) return Expression::Operation::kGtEq;
101+
if (typeStr == kTypeIn) return Expression::Operation::kIn;
102+
if (typeStr == kTypeNotIn) return Expression::Operation::kNotIn;
103+
if (typeStr == kTypeIsNull) return Expression::Operation::kIsNull;
104+
if (typeStr == kTypeNotNull) return Expression::Operation::kNotNull;
105+
if (typeStr == kTypeIsNan) return Expression::Operation::kIsNan;
106+
if (typeStr == kTypeNotNan) return Expression::Operation::kNotNan;
107+
if (typeStr == kTypeStartsWith) return Expression::Operation::kStartsWith;
108+
if (typeStr == kTypeNotStartsWith) return Expression::Operation::kNotStartsWith;
109+
if (typeStr == kCount) return Expression::Operation::kCount;
110+
if (typeStr == kCountNull) return Expression::Operation::kCountNull;
111+
if (typeStr == kCountStar) return Expression::Operation::kCountStar;
112+
if (typeStr == kMin) return Expression::Operation::kMin;
113+
if (typeStr == kMax) return Expression::Operation::kMax;
114+
115+
return JsonParseError("Unknown expression type: {}", typeStr);
116+
}
117+
118+
nlohmann::json ToJson(Expression::Operation op) {
119+
std::string json(ToString(op));
120+
std::ranges::transform(json, json.begin(), [](unsigned char c) -> char {
121+
return (c == '_') ? '-' : static_cast<char>(std::tolower(c));
122+
});
123+
return json;
124+
}
125+
126+
Result<std::shared_ptr<Expression>> ExpressionFromJson(const nlohmann::json& json) {
127+
// Handle boolean
128+
if (json.is_boolean()) {
129+
return json.get<bool>()
130+
? internal::checked_pointer_cast<Expression>(True::Instance())
131+
: internal::checked_pointer_cast<Expression>(False::Instance());
132+
}
133+
return JsonParseError("Only booleans are currently supported.");
134+
}
135+
136+
nlohmann::json ToJson(const Expression& expr) {
137+
switch (expr.op()) {
138+
case Expression::Operation::kTrue:
139+
return true;
140+
141+
case Expression::Operation::kFalse:
142+
return false;
143+
default:
144+
// TODO(evindj): This code will be removed as we implemented the full expression
145+
// serialization.
146+
ICEBERG_CHECK_OR_DIE(false, "Only booleans are currently supported.");
147+
}
148+
}
149+
150+
} // namespace iceberg
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
#include <nlohmann/json_fwd.hpp>
23+
24+
#include "iceberg/expression/expression.h"
25+
#include "iceberg/iceberg_export.h"
26+
#include "iceberg/result.h"
27+
#include "iceberg/type_fwd.h"
28+
29+
/// \file iceberg/expression/json_serde_internal.h
30+
/// JSON serialization and deserialization for expressions.
31+
32+
namespace iceberg {
33+
34+
/// \brief Converts an operation type string to an Expression::Operation.
35+
///
36+
/// \param typeStr The operation type string
37+
/// \return The corresponding Operation or an error if unknown
38+
ICEBERG_EXPORT Result<Expression::Operation> OperationTypeFromJson(
39+
const nlohmann::json& json);
40+
41+
/// \brief Converts an Expression::Operation to its json representation.
42+
///
43+
/// \param op The operation to convert
44+
/// \return The operation type string (e.g., "eq", "lt-eq", "is-null")
45+
ICEBERG_EXPORT nlohmann::json ToJson(Expression::Operation op);
46+
47+
/// \brief Deserializes a JSON object into an Expression.
48+
///
49+
/// \param json A JSON object representing an expression
50+
/// \return A shared pointer to the deserialized Expression or an error
51+
ICEBERG_EXPORT Result<std::shared_ptr<Expression>> ExpressionFromJson(
52+
const nlohmann::json& json);
53+
54+
/// \brief Serializes an Expression into its JSON representation.
55+
///
56+
/// \param expr The expression to serialize
57+
/// \return A JSON object representing the expression
58+
ICEBERG_EXPORT nlohmann::json ToJson(const Expression& expr);
59+
60+
/// Check if an operation is a unary predicate
61+
ICEBERG_EXPORT bool IsUnaryOperation(Expression::Operation op);
62+
63+
/// Check if an operation is a set predicate
64+
ICEBERG_EXPORT bool IsSetOperation(Expression::Operation op);
65+
66+
} // namespace iceberg

src/iceberg/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ iceberg_sources = files(
4949
'expression/expression.cc',
5050
'expression/expressions.cc',
5151
'expression/inclusive_metrics_evaluator.cc',
52+
'expression/json_serde.cc',
5253
'expression/literal.cc',
5354
'expression/manifest_evaluator.cc',
5455
'expression/predicate.cc',

src/iceberg/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ add_iceberg_test(table_test
8888
add_iceberg_test(expression_test
8989
SOURCES
9090
aggregate_test.cc
91+
expression_json_test.cc
9192
expression_test.cc
9293
expression_visitor_test.cc
9394
inclusive_metrics_evaluator_test.cc
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include <memory>
21+
#include <vector>
22+
23+
#include <gmock/gmock.h>
24+
#include <gtest/gtest.h>
25+
#include <nlohmann/json.hpp>
26+
27+
#include "iceberg/expression/expression.h"
28+
#include "iceberg/expression/expressions.h"
29+
#include "iceberg/expression/json_serde_internal.h"
30+
#include "iceberg/expression/literal.h"
31+
#include "iceberg/expression/predicate.h"
32+
#include "iceberg/expression/term.h"
33+
#include "iceberg/test/matchers.h"
34+
35+
namespace iceberg {
36+
37+
// Test boolean constant expressions
38+
TEST(ExpressionJsonTest, CheckBooleanExpression) {
39+
auto checkBoolean = [](std::shared_ptr<Expression> expr, bool value) {
40+
auto json = ToJson(*expr);
41+
EXPECT_TRUE(json.is_boolean());
42+
EXPECT_EQ(json.get<bool>(), value);
43+
44+
auto result = ExpressionFromJson(json);
45+
ASSERT_THAT(result, IsOk());
46+
if (value) {
47+
EXPECT_EQ(result.value()->op(), Expression::Operation::kTrue);
48+
} else {
49+
EXPECT_EQ(result.value()->op(), Expression::Operation::kFalse);
50+
}
51+
};
52+
checkBoolean(True::Instance(), true);
53+
checkBoolean(False::Instance(), false);
54+
}
55+
56+
TEST(ExpressionJsonTest, OperationTypeTests) {
57+
EXPECT_EQ(OperationTypeFromJson("true"), Expression::Operation::kTrue);
58+
EXPECT_EQ("true", ToJson(Expression::Operation::kTrue));
59+
EXPECT_TRUE(IsSetOperation(Expression::Operation::kIn));
60+
EXPECT_FALSE(IsSetOperation(Expression::Operation::kTrue));
61+
62+
EXPECT_TRUE(IsUnaryOperation(Expression::Operation::kIsNull));
63+
EXPECT_FALSE(IsUnaryOperation(Expression::Operation::kTrue));
64+
}
65+
66+
} // namespace iceberg

src/iceberg/test/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ iceberg_tests = {
6161
'expression_test': {
6262
'sources': files(
6363
'aggregate_test.cc',
64+
'expression_json_test.cc',
6465
'expression_test.cc',
6566
'expression_visitor_test.cc',
6667
'inclusive_metrics_evaluator_test.cc',

0 commit comments

Comments
 (0)