Skip to content

Commit 940eace

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

File tree

7 files changed

+306
-0
lines changed

7 files changed

+306
-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: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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 <string>
22+
#include <utility>
23+
#include <vector>
24+
25+
#include <nlohmann/json.hpp>
26+
27+
#include "iceberg/expression/expressions.h"
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+
} // namespace
57+
58+
bool IsUnaryOperation(Expression::Operation op) {
59+
switch (op) {
60+
case Expression::Operation::kIsNull:
61+
case Expression::Operation::kNotNull:
62+
case Expression::Operation::kIsNan:
63+
case Expression::Operation::kNotNan:
64+
return true;
65+
default:
66+
return false;
67+
}
68+
}
69+
70+
bool IsSetOperation(Expression::Operation op) {
71+
switch (op) {
72+
case Expression::Operation::kIn:
73+
case Expression::Operation::kNotIn:
74+
return true;
75+
default:
76+
return false;
77+
}
78+
}
79+
80+
Result<Expression::Operation> OperationTypeFromString(const std::string_view typeStr) {
81+
if (typeStr == kTypeTrue) return Expression::Operation::kTrue;
82+
if (typeStr == kTypeFalse) return Expression::Operation::kFalse;
83+
if (typeStr == kTypeAnd) return Expression::Operation::kAnd;
84+
if (typeStr == kTypeOr) return Expression::Operation::kOr;
85+
if (typeStr == kTypeNot) return Expression::Operation::kNot;
86+
if (typeStr == kTypeEq) return Expression::Operation::kEq;
87+
if (typeStr == kTypeNotEq) return Expression::Operation::kNotEq;
88+
if (typeStr == kTypeLt) return Expression::Operation::kLt;
89+
if (typeStr == kTypeLtEq) return Expression::Operation::kLtEq;
90+
if (typeStr == kTypeGt) return Expression::Operation::kGt;
91+
if (typeStr == kTypeGtEq) return Expression::Operation::kGtEq;
92+
if (typeStr == kTypeIn) return Expression::Operation::kIn;
93+
if (typeStr == kTypeNotIn) return Expression::Operation::kNotIn;
94+
if (typeStr == kTypeIsNull) return Expression::Operation::kIsNull;
95+
if (typeStr == kTypeNotNull) return Expression::Operation::kNotNull;
96+
if (typeStr == kTypeIsNan) return Expression::Operation::kIsNan;
97+
if (typeStr == kTypeNotNan) return Expression::Operation::kNotNan;
98+
if (typeStr == kTypeStartsWith) return Expression::Operation::kStartsWith;
99+
if (typeStr == kTypeNotStartsWith) return Expression::Operation::kNotStartsWith;
100+
101+
return JsonParseError("Unknown expression type: {}", typeStr);
102+
}
103+
104+
std::string_view ToStringOperationType(Expression::Operation op) {
105+
switch (op) {
106+
case Expression::Operation::kTrue:
107+
return kTypeTrue;
108+
case Expression::Operation::kFalse:
109+
return kTypeFalse;
110+
case Expression::Operation::kAnd:
111+
return kTypeAnd;
112+
case Expression::Operation::kOr:
113+
return kTypeOr;
114+
case Expression::Operation::kNot:
115+
return kTypeNot;
116+
case Expression::Operation::kEq:
117+
return kTypeEq;
118+
case Expression::Operation::kNotEq:
119+
return kTypeNotEq;
120+
case Expression::Operation::kLt:
121+
return kTypeLt;
122+
case Expression::Operation::kLtEq:
123+
return kTypeLtEq;
124+
case Expression::Operation::kGt:
125+
return kTypeGt;
126+
case Expression::Operation::kGtEq:
127+
return kTypeGtEq;
128+
case Expression::Operation::kIn:
129+
return kTypeIn;
130+
case Expression::Operation::kNotIn:
131+
return kTypeNotIn;
132+
case Expression::Operation::kIsNull:
133+
return kTypeIsNull;
134+
case Expression::Operation::kNotNull:
135+
return kTypeNotNull;
136+
case Expression::Operation::kIsNan:
137+
return kTypeIsNan;
138+
case Expression::Operation::kNotNan:
139+
return kTypeNotNan;
140+
case Expression::Operation::kStartsWith:
141+
return kTypeStartsWith;
142+
case Expression::Operation::kNotStartsWith:
143+
return kTypeNotStartsWith;
144+
default:
145+
ICEBERG_CHECK_OR_DIE(false, "Unknown expression operation.");
146+
}
147+
}
148+
149+
Result<std::shared_ptr<Expression>> ExpressionFromJson(const nlohmann::json& json) {
150+
// Handle boolean
151+
if (json.is_boolean()) {
152+
return json.get<bool>()
153+
? internal::checked_pointer_cast<Expression>(True::Instance())
154+
: internal::checked_pointer_cast<Expression>(False::Instance());
155+
}
156+
return JsonParseError("Only booleans are currently supported.");
157+
}
158+
159+
nlohmann::json ExpressionToJson(const Expression& expr) {
160+
switch (expr.op()) {
161+
case Expression::Operation::kTrue:
162+
return true;
163+
164+
case Expression::Operation::kFalse:
165+
return false;
166+
default:
167+
ICEBERG_CHECK_OR_DIE(false, "Only booleans are currently supported.");
168+
}
169+
}
170+
171+
} // namespace iceberg
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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/iceberg_export.h"
25+
#include "iceberg/result.h"
26+
#include "iceberg/type_fwd.h"
27+
28+
/// \file iceberg/expression/json_serde_internal.h
29+
/// JSON serialization and deserialization for expressions.
30+
31+
namespace iceberg {
32+
33+
/// \brief Converts an operation type string to an Expression::Operation.
34+
///
35+
/// \param typeStr The operation type string
36+
/// \return The corresponding Operation or an error if unknown
37+
ICEBERG_EXPORT Result<Expression::Operation> OperationTypeFromString(
38+
const std::string_view typeStr);
39+
40+
/// \brief Converts an Expression::Operation to its string representation.
41+
///
42+
/// \param op The operation to convert
43+
/// \return The operation type string (e.g., "eq", "lt-eq", "is-null")
44+
ICEBERG_EXPORT std::string_view ToStringOperationType(Expression::Operation op);
45+
46+
/// \brief Deserializes a JSON object into an Expression.
47+
///
48+
/// \param json A JSON object representing an expression
49+
/// \return A shared pointer to the deserialized Expression or an error
50+
ICEBERG_EXPORT Result<std::shared_ptr<Expression>> ExpressionFromJson(
51+
const nlohmann::json& json);
52+
53+
/// \brief Serializes an Expression into its JSON representation.
54+
///
55+
/// \param expr The expression to serialize
56+
/// \return A JSON object representing the expression
57+
ICEBERG_EXPORT nlohmann::json ExpressionToJson(const Expression& expr);
58+
59+
/// Check if an operation is a unary predicate
60+
ICEBERG_EXPORT bool IsUnaryOperation(Expression::Operation op);
61+
62+
/// Check if an operation is a set predicate
63+
ICEBERG_EXPORT bool IsSetOperation(Expression::Operation op);
64+
65+
} // 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 = ExpressionToJson(*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(OperationTypeFromString("true"), Expression::Operation::kTrue);
58+
EXPECT_EQ("true", ToStringOperationType(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)