This is an automated email from the ASF dual-hosted git repository.
gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 018df25 refactor: use fluent api in the expressions factory and throw
`ExpressionError` (#283)
018df25 is described below
commit 018df25c1e85b19b9102193dd9880eff4e295c2b
Author: Li Feiyang <[email protected]>
AuthorDate: Tue Nov 4 14:08:42 2025 +0800
refactor: use fluent api in the expressions factory and throw
`ExpressionError` (#283)
---
src/iceberg/exception.h | 6 +++
src/iceberg/expression/expression.cc | 41 ++++++++++++---
src/iceberg/expression/expression.h | 21 +++++---
src/iceberg/expression/expressions.cc | 42 ++++++++++-----
src/iceberg/expression/expressions.h | 14 +++--
src/iceberg/expression/term.cc | 63 ++++++++++++++++++++---
src/iceberg/expression/term.h | 30 ++++++-----
src/iceberg/schema_field.cc | 10 ++++
src/iceberg/schema_field.h | 3 ++
src/iceberg/test/expression_test.cc | 96 ++++++++++++++++++++++++++---------
src/iceberg/test/predicate_test.cc | 12 ++---
src/iceberg/util/macros.h | 9 +++-
12 files changed, 268 insertions(+), 79 deletions(-)
diff --git a/src/iceberg/exception.h b/src/iceberg/exception.h
index c5160bc..bbb9c22 100644
--- a/src/iceberg/exception.h
+++ b/src/iceberg/exception.h
@@ -38,6 +38,12 @@ class ICEBERG_EXPORT IcebergError : public
std::runtime_error {
explicit IcebergError(const std::string& what) : std::runtime_error(what) {}
};
+/// \brief Exception thrown when expression construction fails.
+class ICEBERG_EXPORT ExpressionError : public IcebergError {
+ public:
+ explicit ExpressionError(const std::string& what) : IcebergError(what) {}
+};
+
#define ICEBERG_CHECK(condition, ...) \
do { \
if (!(condition)) [[unlikely]] { \
diff --git a/src/iceberg/expression/expression.cc
b/src/iceberg/expression/expression.cc
index 070d5fe..69446a8 100644
--- a/src/iceberg/expression/expression.cc
+++ b/src/iceberg/expression/expression.cc
@@ -45,8 +45,18 @@ const std::shared_ptr<False>& False::Instance() {
Result<std::shared_ptr<Expression>> False::Negate() const { return
True::Instance(); }
// And implementation
+Result<std::unique_ptr<And>> And::Make(std::shared_ptr<Expression> left,
+ std::shared_ptr<Expression> right) {
+ if (!left || !right) [[unlikely]] {
+ return InvalidExpression("And expression cannot have null children");
+ }
+ return std::unique_ptr<And>(new And(std::move(left), std::move(right)));
+}
+
And::And(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right)
- : left_(std::move(left)), right_(std::move(right)) {}
+ : left_(std::move(left)), right_(std::move(right)) {
+ ICEBERG_DCHECK(left_ && right_, "And cannot have null children");
+}
std::string And::ToString() const {
return std::format("({} and {})", left_->ToString(), right_->ToString());
@@ -56,7 +66,7 @@ Result<std::shared_ptr<Expression>> And::Negate() const {
// De Morgan's law: not(A and B) = (not A) or (not B)
ICEBERG_ASSIGN_OR_RAISE(auto left_negated, left_->Negate());
ICEBERG_ASSIGN_OR_RAISE(auto right_negated, right_->Negate());
- return std::make_shared<Or>(std::move(left_negated),
std::move(right_negated));
+ return Or::Make(std::move(left_negated), std::move(right_negated));
}
bool And::Equals(const Expression& expr) const {
@@ -69,8 +79,18 @@ bool And::Equals(const Expression& expr) const {
}
// Or implementation
+Result<std::unique_ptr<Or>> Or::Make(std::shared_ptr<Expression> left,
+ std::shared_ptr<Expression> right) {
+ if (!left || !right) [[unlikely]] {
+ return InvalidExpression("Or cannot have null children");
+ }
+ return std::unique_ptr<Or>(new Or(std::move(left), std::move(right)));
+}
+
Or::Or(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right)
- : left_(std::move(left)), right_(std::move(right)) {}
+ : left_(std::move(left)), right_(std::move(right)) {
+ ICEBERG_DCHECK(left_ && right_, "Or cannot have null children");
+}
std::string Or::ToString() const {
return std::format("({} or {})", left_->ToString(), right_->ToString());
@@ -80,7 +100,7 @@ Result<std::shared_ptr<Expression>> Or::Negate() const {
// De Morgan's law: not(A or B) = (not A) and (not B)
ICEBERG_ASSIGN_OR_RAISE(auto left_negated, left_->Negate());
ICEBERG_ASSIGN_OR_RAISE(auto right_negated, right_->Negate());
- return std::make_shared<And>(std::move(left_negated),
std::move(right_negated));
+ return And::Make(std::move(left_negated), std::move(right_negated));
}
bool Or::Equals(const Expression& expr) const {
@@ -93,7 +113,16 @@ bool Or::Equals(const Expression& expr) const {
}
// Not implementation
-Not::Not(std::shared_ptr<Expression> child) : child_(std::move(child)) {}
+Result<std::unique_ptr<Not>> Not::Make(std::shared_ptr<Expression> child) {
+ if (!child) [[unlikely]] {
+ return InvalidExpression("Not expression cannot have null child");
+ }
+ return std::unique_ptr<Not>(new Not(std::move(child)));
+}
+
+Not::Not(std::shared_ptr<Expression> child) : child_(std::move(child)) {
+ ICEBERG_DCHECK(child_ != nullptr, "Not expression cannot have null child");
+}
std::string Not::ToString() const { return std::format("not({})",
child_->ToString()); }
@@ -199,7 +228,7 @@ Result<Expression::Operation> Negate(Expression::Operation
op) {
case Expression::Operation::kMax:
case Expression::Operation::kMin:
case Expression::Operation::kCount:
- return InvalidArgument("No negation for operation: {}", op);
+ return InvalidExpression("No negation for operation: {}", op);
}
std::unreachable();
}
diff --git a/src/iceberg/expression/expression.h
b/src/iceberg/expression/expression.h
index 9a80522..931e35c 100644
--- a/src/iceberg/expression/expression.h
+++ b/src/iceberg/expression/expression.h
@@ -130,11 +130,12 @@ class ICEBERG_EXPORT False : public Expression {
/// evaluate to true.
class ICEBERG_EXPORT And : public Expression {
public:
- /// \brief Constructs an And expression from two sub-expressions.
+ /// \brief Creates an And expression from two sub-expressions.
///
/// \param left The left operand of the AND expression
/// \param right The right operand of the AND expression
- And(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right);
+ static Result<std::unique_ptr<And>> Make(std::shared_ptr<Expression> left,
+ std::shared_ptr<Expression> right);
/// \brief Returns the left operand of the AND expression.
///
@@ -155,6 +156,8 @@ class ICEBERG_EXPORT And : public Expression {
bool Equals(const Expression& other) const override;
private:
+ And(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right);
+
std::shared_ptr<Expression> left_;
std::shared_ptr<Expression> right_;
};
@@ -165,11 +168,12 @@ class ICEBERG_EXPORT And : public Expression {
/// evaluates to true.
class ICEBERG_EXPORT Or : public Expression {
public:
- /// \brief Constructs an Or expression from two sub-expressions.
+ /// \brief Creates an Or expression from two sub-expressions.
///
/// \param left The left operand of the OR expression
/// \param right The right operand of the OR expression
- Or(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right);
+ static Result<std::unique_ptr<Or>> Make(std::shared_ptr<Expression> left,
+ std::shared_ptr<Expression> right);
/// \brief Returns the left operand of the OR expression.
///
@@ -190,6 +194,8 @@ class ICEBERG_EXPORT Or : public Expression {
bool Equals(const Expression& other) const override;
private:
+ Or(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right);
+
std::shared_ptr<Expression> left_;
std::shared_ptr<Expression> right_;
};
@@ -199,10 +205,11 @@ class ICEBERG_EXPORT Or : public Expression {
/// This expression negates its child expression.
class ICEBERG_EXPORT Not : public Expression {
public:
- /// \brief Constructs a Not expression from a child expression.
+ /// \brief Creates a Not expression from a child expression.
///
/// \param child The expression to negate
- explicit Not(std::shared_ptr<Expression> child);
+ /// \return A Result containing a unique pointer to Not, or an error if
child is nullptr
+ static Result<std::unique_ptr<Not>> Make(std::shared_ptr<Expression> child);
/// \brief Returns the child expression.
///
@@ -218,6 +225,8 @@ class ICEBERG_EXPORT Not : public Expression {
bool Equals(const Expression& other) const override;
private:
+ explicit Not(std::shared_ptr<Expression> child);
+
std::shared_ptr<Expression> child_;
};
diff --git a/src/iceberg/expression/expressions.cc
b/src/iceberg/expression/expressions.cc
index 686a55d..6839157 100644
--- a/src/iceberg/expression/expressions.cc
+++ b/src/iceberg/expression/expressions.cc
@@ -22,6 +22,7 @@
#include "iceberg/exception.h"
#include "iceberg/transform.h"
#include "iceberg/type.h"
+#include "iceberg/util/macros.h"
namespace iceberg {
@@ -41,41 +42,57 @@ std::shared_ptr<Expression>
Expressions::Not(std::shared_ptr<Expression> child)
return not_expr.child();
}
- return std::make_shared<::iceberg::Not>(std::move(child));
+ ICEBERG_ASSIGN_OR_THROW(auto not_expr, iceberg::Not::Make(std::move(child)));
+ return not_expr;
}
// Transform functions
std::shared_ptr<UnboundTransform> Expressions::Bucket(std::string name,
int32_t num_buckets) {
- return std::make_shared<UnboundTransform>(Ref(std::move(name)),
- Transform::Bucket(num_buckets));
+ ICEBERG_ASSIGN_OR_THROW(
+ auto transform,
+ UnboundTransform::Make(Ref(std::move(name)),
Transform::Bucket(num_buckets)));
+ return transform;
}
std::shared_ptr<UnboundTransform> Expressions::Year(std::string name) {
- return std::make_shared<UnboundTransform>(Ref(std::move(name)),
Transform::Year());
+ ICEBERG_ASSIGN_OR_THROW(
+ auto transform, UnboundTransform::Make(Ref(std::move(name)),
Transform::Year()));
+ return transform;
}
std::shared_ptr<UnboundTransform> Expressions::Month(std::string name) {
- return std::make_shared<UnboundTransform>(Ref(std::move(name)),
Transform::Month());
+ ICEBERG_ASSIGN_OR_THROW(
+ auto transform, UnboundTransform::Make(Ref(std::move(name)),
Transform::Month()));
+ return transform;
}
std::shared_ptr<UnboundTransform> Expressions::Day(std::string name) {
- return std::make_shared<UnboundTransform>(Ref(std::move(name)),
Transform::Day());
+ ICEBERG_ASSIGN_OR_THROW(auto transform,
+ UnboundTransform::Make(Ref(std::move(name)),
Transform::Day()));
+ return transform;
}
std::shared_ptr<UnboundTransform> Expressions::Hour(std::string name) {
- return std::make_shared<UnboundTransform>(Ref(std::move(name)),
Transform::Hour());
+ ICEBERG_ASSIGN_OR_THROW(
+ auto transform, UnboundTransform::Make(Ref(std::move(name)),
Transform::Hour()));
+ return transform;
}
std::shared_ptr<UnboundTransform> Expressions::Truncate(std::string name,
int32_t width) {
- return std::make_shared<UnboundTransform>(Ref(std::move(name)),
- Transform::Truncate(width));
+ ICEBERG_ASSIGN_OR_THROW(
+ auto transform,
+ UnboundTransform::Make(Ref(std::move(name)),
Transform::Truncate(width)));
+ return transform;
}
std::shared_ptr<UnboundTransform> Expressions::Transform(
std::string name, std::shared_ptr<::iceberg::Transform> transform) {
- return std::make_shared<UnboundTransform>(Ref(std::move(name)),
std::move(transform));
+ ICEBERG_ASSIGN_OR_THROW(
+ auto unbound_transform,
+ UnboundTransform::Make(Ref(std::move(name)), std::move(transform)));
+ return unbound_transform;
}
// Template implementations for unary predicates
@@ -327,11 +344,12 @@ std::shared_ptr<False> Expressions::AlwaysFalse() {
return False::Instance(); }
// Utilities
std::shared_ptr<NamedReference> Expressions::Ref(std::string name) {
- return std::make_shared<NamedReference>(std::move(name));
+ ICEBERG_ASSIGN_OR_THROW(auto ref, NamedReference::Make(std::move(name)));
+ return ref;
}
Literal Expressions::Lit(Literal::Value value, std::shared_ptr<PrimitiveType>
type) {
- throw IcebergError("Literal creation is not implemented");
+ throw ExpressionError("Literal creation is not implemented");
}
} // namespace iceberg
diff --git a/src/iceberg/expression/expressions.h
b/src/iceberg/expression/expressions.h
index 66083da..13895bd 100644
--- a/src/iceberg/expression/expressions.h
+++ b/src/iceberg/expression/expressions.h
@@ -27,14 +27,18 @@
#include <string>
#include <vector>
+#include "iceberg/exception.h"
#include "iceberg/expression/literal.h"
#include "iceberg/expression/predicate.h"
#include "iceberg/expression/term.h"
#include "iceberg/iceberg_export.h"
+#include "iceberg/util/macros.h"
namespace iceberg {
-/// \brief Factory methods for creating expressions.
+/// \brief Fluent APIs to create expressions.
+///
+/// \throw `ExpressionError` for invalid expression.
class ICEBERG_EXPORT Expressions {
public:
// Logical operations
@@ -60,7 +64,9 @@ class ICEBERG_EXPORT Expressions {
return left;
}
- return std::make_shared<::iceberg::And>(std::move(left),
std::move(right));
+ ICEBERG_ASSIGN_OR_THROW(auto and_expr,
+ iceberg::And::Make(std::move(left),
std::move(right)));
+ return and_expr;
} else {
return And(And(std::move(left), std::move(right)),
std::forward<Args>(args)...);
}
@@ -86,7 +92,9 @@ class ICEBERG_EXPORT Expressions {
return left;
}
- return std::make_shared<::iceberg::Or>(std::move(left),
std::move(right));
+ ICEBERG_ASSIGN_OR_THROW(auto or_expr,
+ iceberg::Or::Make(std::move(left),
std::move(right)));
+ return or_expr;
} else {
return Or(Or(std::move(left), std::move(right)),
std::forward<Args>(args)...);
}
diff --git a/src/iceberg/expression/term.cc b/src/iceberg/expression/term.cc
index 30bdf8e..ba6e55e 100644
--- a/src/iceberg/expression/term.cc
+++ b/src/iceberg/expression/term.cc
@@ -42,8 +42,17 @@ Result<std::shared_ptr<B>> Unbound<B>::Bind(const Schema&
schema) const {
}
// NamedReference implementation
+Result<std::unique_ptr<NamedReference>> NamedReference::Make(std::string
field_name) {
+ if (field_name.empty()) [[unlikely]] {
+ return InvalidExpression("NamedReference cannot have empty field name");
+ }
+ return std::unique_ptr<NamedReference>(new
NamedReference(std::move(field_name)));
+}
+
NamedReference::NamedReference(std::string field_name)
- : field_name_(std::move(field_name)) {}
+ : field_name_(std::move(field_name)) {
+ ICEBERG_DCHECK(!field_name_.empty(), "NamedReference cannot have empty field
name");
+}
NamedReference::~NamedReference() = default;
@@ -51,11 +60,11 @@ Result<std::shared_ptr<BoundReference>>
NamedReference::Bind(const Schema& schem
bool
case_sensitive) const {
ICEBERG_ASSIGN_OR_RAISE(auto field_opt,
schema.GetFieldByName(field_name_, case_sensitive));
- if (!field_opt.has_value()) {
+ if (!field_opt.has_value()) [[unlikely]] {
return InvalidExpression("Cannot find field '{}' in struct: {}",
field_name_,
schema.ToString());
}
- return std::make_shared<BoundReference>(field_opt.value().get());
+ return BoundReference::Make(field_opt.value().get());
}
std::string NamedReference::ToString() const {
@@ -63,7 +72,18 @@ std::string NamedReference::ToString() const {
}
// BoundReference implementation
-BoundReference::BoundReference(SchemaField field) : field_(std::move(field)) {}
+Result<std::unique_ptr<BoundReference>> BoundReference::Make(SchemaField
field) {
+ if (auto status = field.Validate(); !status.has_value()) [[unlikely]] {
+ return InvalidExpression("Cannot create BoundReference with invalid field:
{}",
+ status.error().message);
+ }
+ return std::unique_ptr<BoundReference>(new BoundReference(std::move(field)));
+}
+
+BoundReference::BoundReference(SchemaField field) : field_(std::move(field)) {
+ ICEBERG_DCHECK(field_.Validate().has_value(),
+ "Cannot create BoundReference with invalid field");
+}
BoundReference::~BoundReference() = default;
@@ -87,9 +107,22 @@ bool BoundReference::Equals(const BoundTerm& other) const {
}
// UnboundTransform implementation
+Result<std::unique_ptr<UnboundTransform>> UnboundTransform::Make(
+ std::shared_ptr<NamedReference> ref, std::shared_ptr<Transform> transform)
{
+ if (!ref || !transform) [[unlikely]] {
+ return InvalidExpression(
+ "Cannot create UnboundTransform with null reference or transform");
+ }
+ return std::unique_ptr<UnboundTransform>(
+ new UnboundTransform(std::move(ref), std::move(transform)));
+}
+
UnboundTransform::UnboundTransform(std::shared_ptr<NamedReference> ref,
std::shared_ptr<Transform> transform)
- : ref_(std::move(ref)), transform_(std::move(transform)) {}
+ : ref_(std::move(ref)), transform_(std::move(transform)) {
+ ICEBERG_DCHECK(!ref || !transform,
+ "Cannot create UnboundTransform with null reference or
transform");
+}
UnboundTransform::~UnboundTransform() = default;
@@ -101,17 +134,31 @@ Result<std::shared_ptr<BoundTransform>>
UnboundTransform::Bind(
const Schema& schema, bool case_sensitive) const {
ICEBERG_ASSIGN_OR_RAISE(auto bound_ref, ref_->Bind(schema, case_sensitive));
ICEBERG_ASSIGN_OR_RAISE(auto transform_func,
transform_->Bind(bound_ref->type()));
- return std::make_shared<BoundTransform>(std::move(bound_ref), transform_,
- std::move(transform_func));
+ return BoundTransform::Make(std::move(bound_ref), transform_,
+ std::move(transform_func));
}
// BoundTransform implementation
+Result<std::unique_ptr<BoundTransform>> BoundTransform::Make(
+ std::shared_ptr<BoundReference> ref, std::shared_ptr<Transform> transform,
+ std::shared_ptr<TransformFunction> transform_func) {
+ if (!ref || !transform || !transform_func) [[unlikely]] {
+ return InvalidExpression(
+ "Cannot create BoundTransform with null reference or transform");
+ }
+ return std::unique_ptr<BoundTransform>(new BoundTransform(
+ std::move(ref), std::move(transform), std::move(transform_func)));
+}
+
BoundTransform::BoundTransform(std::shared_ptr<BoundReference> ref,
std::shared_ptr<Transform> transform,
std::shared_ptr<TransformFunction>
transform_func)
: ref_(std::move(ref)),
transform_(std::move(transform)),
- transform_func_(std::move(transform_func)) {}
+ transform_func_(std::move(transform_func)) {
+ ICEBERG_DCHECK(ref_ && transform_ && transform_func_,
+ "Cannot create BoundTransform with null reference or
transform");
+}
BoundTransform::~BoundTransform() = default;
diff --git a/src/iceberg/expression/term.h b/src/iceberg/expression/term.h
index e0a883c..6259b82 100644
--- a/src/iceberg/expression/term.h
+++ b/src/iceberg/expression/term.h
@@ -32,11 +32,6 @@
namespace iceberg {
-// TODO(gangwu): add a struct-like interface to wrap a row of data from
ArrowArray or
-// structs like ManifestFile and ManifestEntry to facilitate generailization
of the
-// evaluation of expressions on top of different data structures.
-class StructLike;
-
/// \brief A term is an expression node that produces a typed value when
evaluated.
class ICEBERG_EXPORT Term : public util::Formattable {
public:
@@ -138,7 +133,7 @@ class ICEBERG_EXPORT NamedReference
/// \brief Create a named reference to a field.
///
/// \param field_name The name of the field to reference
- explicit NamedReference(std::string field_name);
+ static Result<std::unique_ptr<NamedReference>> Make(std::string field_name);
~NamedReference() override;
@@ -154,6 +149,8 @@ class ICEBERG_EXPORT NamedReference
Kind kind() const override { return Kind::kReference; }
private:
+ explicit NamedReference(std::string field_name);
+
std::string field_name_;
};
@@ -166,7 +163,7 @@ class ICEBERG_EXPORT BoundReference
/// \brief Create a bound reference.
///
/// \param field The schema field
- explicit BoundReference(SchemaField field);
+ static Result<std::unique_ptr<BoundReference>> Make(SchemaField field);
~BoundReference() override;
@@ -189,6 +186,8 @@ class ICEBERG_EXPORT BoundReference
Kind kind() const override { return Kind::kReference; }
private:
+ explicit BoundReference(SchemaField field);
+
SchemaField field_;
};
@@ -199,8 +198,8 @@ class ICEBERG_EXPORT UnboundTransform : public
UnboundTerm<class BoundTransform>
///
/// \param ref The term to apply the transformation to
/// \param transform The transformation function to apply
- UnboundTransform(std::shared_ptr<NamedReference> ref,
- std::shared_ptr<Transform> transform);
+ static Result<std::unique_ptr<UnboundTransform>> Make(
+ std::shared_ptr<NamedReference> ref, std::shared_ptr<Transform>
transform);
~UnboundTransform() override;
@@ -216,6 +215,9 @@ class ICEBERG_EXPORT UnboundTransform : public
UnboundTerm<class BoundTransform>
Kind kind() const override { return Kind::kTransform; }
private:
+ UnboundTransform(std::shared_ptr<NamedReference> ref,
+ std::shared_ptr<Transform> transform);
+
std::shared_ptr<NamedReference> ref_;
std::shared_ptr<Transform> transform_;
};
@@ -228,9 +230,9 @@ class ICEBERG_EXPORT BoundTransform : public BoundTerm {
/// \param ref The bound term to apply the transformation to
/// \param transform The transform to apply
/// \param transform_func The bound transform function to apply
- BoundTransform(std::shared_ptr<BoundReference> ref,
- std::shared_ptr<Transform> transform,
- std::shared_ptr<TransformFunction> transform_func);
+ static Result<std::unique_ptr<BoundTransform>> Make(
+ std::shared_ptr<BoundReference> ref, std::shared_ptr<Transform>
transform,
+ std::shared_ptr<TransformFunction> transform_func);
~BoundTransform() override;
@@ -251,6 +253,10 @@ class ICEBERG_EXPORT BoundTransform : public BoundTerm {
Kind kind() const override { return Kind::kTransform; }
private:
+ BoundTransform(std::shared_ptr<BoundReference> ref,
+ std::shared_ptr<Transform> transform,
+ std::shared_ptr<TransformFunction> transform_func);
+
std::shared_ptr<BoundReference> ref_;
std::shared_ptr<Transform> transform_;
std::shared_ptr<TransformFunction> transform_func_;
diff --git a/src/iceberg/schema_field.cc b/src/iceberg/schema_field.cc
index 04dc35b..04b55a0 100644
--- a/src/iceberg/schema_field.cc
+++ b/src/iceberg/schema_field.cc
@@ -54,6 +54,16 @@ bool SchemaField::optional() const { return optional_; }
std::string_view SchemaField::doc() const { return doc_; }
+Status SchemaField::Validate() const {
+ if (name_.empty()) [[unlikely]] {
+ return InvalidSchema("SchemaField cannot have empty name");
+ }
+ if (type_ == nullptr) [[unlikely]] {
+ return InvalidSchema("SchemaField cannot have null type");
+ }
+ return {};
+}
+
std::string SchemaField::ToString() const {
std::string result = std::format("{} ({}): {} ({}){}", name_, field_id_,
*type_,
optional_ ? "optional" : "required",
diff --git a/src/iceberg/schema_field.h b/src/iceberg/schema_field.h
index e947f20..4190dba 100644
--- a/src/iceberg/schema_field.h
+++ b/src/iceberg/schema_field.h
@@ -29,6 +29,7 @@
#include <string_view>
#include "iceberg/iceberg_export.h"
+#include "iceberg/result.h"
#include "iceberg/type_fwd.h"
#include "iceberg/util/formattable.h"
@@ -72,6 +73,8 @@ class ICEBERG_EXPORT SchemaField : public
iceberg::util::Formattable {
[[nodiscard]] std::string ToString() const override;
+ Status Validate() const;
+
friend bool operator==(const SchemaField& lhs, const SchemaField& rhs) {
return lhs.Equals(rhs);
}
diff --git a/src/iceberg/test/expression_test.cc
b/src/iceberg/test/expression_test.cc
index 326dadc..97e4272 100644
--- a/src/iceberg/test/expression_test.cc
+++ b/src/iceberg/test/expression_test.cc
@@ -55,12 +55,15 @@ TEST(ANDTest, Basic) {
auto true_expr2 = True::Instance();
// Create an AND expression
- auto and_expr = std::make_shared<And>(true_expr1, true_expr2);
+ auto and_expr_result = And::Make(true_expr1, true_expr2);
+ ASSERT_THAT(and_expr_result, IsOk());
+ auto and_expr =
std::shared_ptr<Expression>(std::move(and_expr_result.value()));
EXPECT_EQ(and_expr->op(), Expression::Operation::kAnd);
EXPECT_EQ(and_expr->ToString(), "(true and true)");
- EXPECT_EQ(and_expr->left()->op(), Expression::Operation::kTrue);
- EXPECT_EQ(and_expr->right()->op(), Expression::Operation::kTrue);
+ auto& and_ref = static_cast<const And&>(*and_expr);
+ EXPECT_EQ(and_ref.left()->op(), Expression::Operation::kTrue);
+ EXPECT_EQ(and_ref.right()->op(), Expression::Operation::kTrue);
}
TEST(ORTest, Basic) {
@@ -69,12 +72,15 @@ TEST(ORTest, Basic) {
auto false_expr = False::Instance();
// Create an OR expression
- auto or_expr = std::make_shared<Or>(true_expr, false_expr);
+ auto or_expr_result = Or::Make(true_expr, false_expr);
+ ASSERT_THAT(or_expr_result, IsOk());
+ auto or_expr =
std::shared_ptr<Expression>(std::move(or_expr_result.value()));
EXPECT_EQ(or_expr->op(), Expression::Operation::kOr);
EXPECT_EQ(or_expr->ToString(), "(true or false)");
- EXPECT_EQ(or_expr->left()->op(), Expression::Operation::kTrue);
- EXPECT_EQ(or_expr->right()->op(), Expression::Operation::kFalse);
+ auto& or_ref = static_cast<const Or&>(*or_expr);
+ EXPECT_EQ(or_ref.left()->op(), Expression::Operation::kTrue);
+ EXPECT_EQ(or_ref.right()->op(), Expression::Operation::kFalse);
}
TEST(ORTest, Negation) {
@@ -82,7 +88,9 @@ TEST(ORTest, Negation) {
auto true_expr = True::Instance();
auto false_expr = False::Instance();
- auto or_expr = std::make_shared<Or>(true_expr, false_expr);
+ auto or_expr_result = Or::Make(true_expr, false_expr);
+ ASSERT_THAT(or_expr_result, IsOk());
+ auto or_expr =
std::shared_ptr<Expression>(std::move(or_expr_result.value()));
auto negated_or_result = or_expr->Negate();
ASSERT_THAT(negated_or_result, IsOk());
auto negated_or = negated_or_result.value();
@@ -97,20 +105,31 @@ TEST(ORTest, Equals) {
auto false_expr = False::Instance();
// Test basic equality
- auto or_expr1 = std::make_shared<Or>(true_expr, false_expr);
- auto or_expr2 = std::make_shared<Or>(true_expr, false_expr);
+ auto or_expr1_result = Or::Make(true_expr, false_expr);
+ ASSERT_THAT(or_expr1_result, IsOk());
+ auto or_expr1 =
std::shared_ptr<Expression>(std::move(or_expr1_result.value()));
+
+ auto or_expr2_result = Or::Make(true_expr, false_expr);
+ ASSERT_THAT(or_expr2_result, IsOk());
+ auto or_expr2 =
std::shared_ptr<Expression>(std::move(or_expr2_result.value()));
EXPECT_TRUE(or_expr1->Equals(*or_expr2));
// Test commutativity: (A or B) equals (B or A)
- auto or_expr3 = std::make_shared<Or>(false_expr, true_expr);
+ auto or_expr3_result = Or::Make(false_expr, true_expr);
+ ASSERT_THAT(or_expr3_result, IsOk());
+ auto or_expr3 =
std::shared_ptr<Expression>(std::move(or_expr3_result.value()));
EXPECT_TRUE(or_expr1->Equals(*or_expr3));
// Test inequality with different expressions
- auto or_expr4 = std::make_shared<Or>(true_expr, true_expr);
+ auto or_expr4_result = Or::Make(true_expr, true_expr);
+ ASSERT_THAT(or_expr4_result, IsOk());
+ auto or_expr4 =
std::shared_ptr<Expression>(std::move(or_expr4_result.value()));
EXPECT_FALSE(or_expr1->Equals(*or_expr4));
// Test inequality with different operation types
- auto and_expr = std::make_shared<And>(true_expr, false_expr);
+ auto and_expr_result = And::Make(true_expr, false_expr);
+ ASSERT_THAT(and_expr_result, IsOk());
+ auto and_expr =
std::shared_ptr<Expression>(std::move(and_expr_result.value()));
EXPECT_FALSE(or_expr1->Equals(*and_expr));
}
@@ -119,7 +138,9 @@ TEST(ANDTest, Negation) {
auto true_expr = True::Instance();
auto false_expr = False::Instance();
- auto and_expr = std::make_shared<And>(true_expr, false_expr);
+ auto and_expr_result = And::Make(true_expr, false_expr);
+ ASSERT_THAT(and_expr_result, IsOk());
+ auto and_expr =
std::shared_ptr<Expression>(std::move(and_expr_result.value()));
auto negated_and_result = and_expr->Negate();
ASSERT_THAT(negated_and_result, IsOk());
auto negated_and = negated_and_result.value();
@@ -134,20 +155,31 @@ TEST(ANDTest, Equals) {
auto false_expr = False::Instance();
// Test basic equality
- auto and_expr1 = std::make_shared<And>(true_expr, false_expr);
- auto and_expr2 = std::make_shared<And>(true_expr, false_expr);
+ auto and_expr1_result = And::Make(true_expr, false_expr);
+ ASSERT_THAT(and_expr1_result, IsOk());
+ auto and_expr1 =
std::shared_ptr<Expression>(std::move(and_expr1_result.value()));
+
+ auto and_expr2_result = And::Make(true_expr, false_expr);
+ ASSERT_THAT(and_expr2_result, IsOk());
+ auto and_expr2 =
std::shared_ptr<Expression>(std::move(and_expr2_result.value()));
EXPECT_TRUE(and_expr1->Equals(*and_expr2));
// Test commutativity: (A and B) equals (B and A)
- auto and_expr3 = std::make_shared<And>(false_expr, true_expr);
+ auto and_expr3_result = And::Make(false_expr, true_expr);
+ ASSERT_THAT(and_expr3_result, IsOk());
+ auto and_expr3 =
std::shared_ptr<Expression>(std::move(and_expr3_result.value()));
EXPECT_TRUE(and_expr1->Equals(*and_expr3));
// Test inequality with different expressions
- auto and_expr4 = std::make_shared<And>(true_expr, true_expr);
+ auto and_expr4_result = And::Make(true_expr, true_expr);
+ ASSERT_THAT(and_expr4_result, IsOk());
+ auto and_expr4 =
std::shared_ptr<Expression>(std::move(and_expr4_result.value()));
EXPECT_FALSE(and_expr1->Equals(*and_expr4));
// Test inequality with different operation types
- auto or_expr = std::make_shared<Or>(true_expr, false_expr);
+ auto or_expr_result = Or::Make(true_expr, false_expr);
+ ASSERT_THAT(or_expr_result, IsOk());
+ auto or_expr =
std::shared_ptr<Expression>(std::move(or_expr_result.value()));
EXPECT_FALSE(and_expr1->Equals(*or_expr));
}
@@ -168,17 +200,22 @@ TEST(ExpressionTest, BaseClassNegateErrorOut) {
TEST(NotTest, Basic) {
auto true_expr = True::Instance();
- auto not_expr = std::make_shared<Not>(true_expr);
+ auto not_expr_result = Not::Make(true_expr);
+ ASSERT_THAT(not_expr_result, IsOk());
+ auto not_expr =
std::shared_ptr<Expression>(std::move(not_expr_result.value()));
EXPECT_EQ(not_expr->op(), Expression::Operation::kNot);
EXPECT_EQ(not_expr->ToString(), "not(true)");
- EXPECT_EQ(not_expr->child()->op(), Expression::Operation::kTrue);
+ auto& not_ref = static_cast<const Not&>(*not_expr);
+ EXPECT_EQ(not_ref.child()->op(), Expression::Operation::kTrue);
}
TEST(NotTest, Negation) {
// Test that not(not(x)) = x
auto true_expr = True::Instance();
- auto not_expr = std::make_shared<Not>(true_expr);
+ auto not_expr_result = Not::Make(true_expr);
+ ASSERT_THAT(not_expr_result, IsOk());
+ auto not_expr =
std::shared_ptr<Expression>(std::move(not_expr_result.value()));
auto negated_result = not_expr->Negate();
ASSERT_THAT(negated_result, IsOk());
@@ -193,16 +230,25 @@ TEST(NotTest, Equals) {
auto false_expr = False::Instance();
// Test basic equality
- auto not_expr1 = std::make_shared<Not>(true_expr);
- auto not_expr2 = std::make_shared<Not>(true_expr);
+ auto not_expr1_result = Not::Make(true_expr);
+ ASSERT_THAT(not_expr1_result, IsOk());
+ auto not_expr1 =
std::shared_ptr<Expression>(std::move(not_expr1_result.value()));
+
+ auto not_expr2_result = Not::Make(true_expr);
+ ASSERT_THAT(not_expr2_result, IsOk());
+ auto not_expr2 =
std::shared_ptr<Expression>(std::move(not_expr2_result.value()));
EXPECT_TRUE(not_expr1->Equals(*not_expr2));
// Test inequality with different child expressions
- auto not_expr3 = std::make_shared<Not>(false_expr);
+ auto not_expr3_result = Not::Make(false_expr);
+ ASSERT_THAT(not_expr3_result, IsOk());
+ auto not_expr3 =
std::shared_ptr<Expression>(std::move(not_expr3_result.value()));
EXPECT_FALSE(not_expr1->Equals(*not_expr3));
// Test inequality with different operation types
- auto and_expr = std::make_shared<And>(true_expr, false_expr);
+ auto and_expr_result = And::Make(true_expr, false_expr);
+ ASSERT_THAT(and_expr_result, IsOk());
+ auto and_expr =
std::shared_ptr<Expression>(std::move(and_expr_result.value()));
EXPECT_FALSE(not_expr1->Equals(*and_expr));
}
diff --git a/src/iceberg/test/predicate_test.cc
b/src/iceberg/test/predicate_test.cc
index 5ab7908..532e908 100644
--- a/src/iceberg/test/predicate_test.cc
+++ b/src/iceberg/test/predicate_test.cc
@@ -218,14 +218,14 @@ TEST_F(PredicateTest, ReferenceFactory) {
}
TEST_F(PredicateTest, NamedReferenceBasics) {
- auto ref = std::make_shared<NamedReference>("id");
+ auto ref = Expressions::Ref("id");
EXPECT_EQ(ref->name(), "id");
EXPECT_EQ(ref->ToString(), "ref(name=\"id\")");
EXPECT_EQ(ref->reference(), ref);
}
TEST_F(PredicateTest, NamedReferenceBind) {
- auto ref = std::make_shared<NamedReference>("id");
+ auto ref = Expressions::Ref("id");
auto bound_result = ref->Bind(*schema_, /*case_sensitive=*/true);
ASSERT_THAT(bound_result, IsOk());
@@ -237,15 +237,15 @@ TEST_F(PredicateTest, NamedReferenceBind) {
}
TEST_F(PredicateTest, NamedReferenceBindNonExistentField) {
- auto ref = std::make_shared<NamedReference>("non_existent_field");
+ auto ref = Expressions::Ref("non_existent_field");
auto bound_result = ref->Bind(*schema_, /*case_sensitive=*/true);
EXPECT_THAT(bound_result, IsError(ErrorKind::kInvalidExpression));
}
TEST_F(PredicateTest, BoundReferenceEquality) {
- auto ref1 = std::make_shared<NamedReference>("id");
- auto ref2 = std::make_shared<NamedReference>("id");
- auto ref3 = std::make_shared<NamedReference>("name");
+ auto ref1 = Expressions::Ref("id");
+ auto ref2 = Expressions::Ref("id");
+ auto ref3 = Expressions::Ref("name");
auto bound1 = ref1->Bind(*schema_, true).value();
auto bound2 = ref2->Bind(*schema_, true).value();
diff --git a/src/iceberg/util/macros.h b/src/iceberg/util/macros.h
index 733b07f..50ac13f 100644
--- a/src/iceberg/util/macros.h
+++ b/src/iceberg/util/macros.h
@@ -44,9 +44,16 @@
#define ICEBERG_DCHECK(expr, message) assert((expr) && (message))
+#define ERROR_TO_EXCEPTION(error) \
+ if (error.kind == iceberg::ErrorKind::kInvalidExpression) { \
+ throw iceberg::ExpressionError(error.message); \
+ } else { \
+ throw iceberg::IcebergError(error.message); \
+ }
+
#define ICEBERG_THROW_NOT_OK(result) \
if (auto&& result_name = result; !result_name) [[unlikely]] { \
- throw iceberg::IcebergError(result_name.error().message); \
+ ERROR_TO_EXCEPTION(result_name.error()); \
}
#define ICEBERG_ASSIGN_OR_THROW_IMPL(result_name, lhs, rexpr) \