blob: 35ffbfdfea4c15b805866207def94e0c5c5feafc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once
/// \file iceberg/expression/expression.h
/// Expression interface for Iceberg table operations.
#include <memory>
#include <string>
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"
#include "iceberg/type_fwd.h"
#include "iceberg/util/formattable.h"
#include "iceberg/util/macros.h"
namespace iceberg {
/// \brief Represents a boolean expression tree.
class ICEBERG_EXPORT Expression : public util::Formattable {
public:
/// Operation types for expressions
enum class Operation {
kTrue,
kFalse,
kIsNull,
kNotNull,
kIsNan,
kNotNan,
kLt,
kLtEq,
kGt,
kGtEq,
kEq,
kNotEq,
kIn,
kNotIn,
kNot,
kAnd,
kOr,
kStartsWith,
kNotStartsWith,
kCount,
kCountNull,
kCountStar,
kMax,
kMin
};
virtual ~Expression() = default;
/// \brief Returns the operation for an expression node.
virtual Operation op() const = 0;
/// \brief Returns the negation of this expression, equivalent to not(this).
virtual Result<std::shared_ptr<Expression>> Negate() const {
return NotSupported("Expression cannot be negated");
}
/// \brief Returns whether this expression will accept the same values as another.
/// \param other another expression
/// \return true if the expressions are equivalent
virtual bool Equals(const Expression& other) const {
// only bound predicates can be equivalent
return false;
}
std::string ToString() const override { return "Expression"; }
virtual bool is_unbound_predicate() const { return false; }
virtual bool is_bound_predicate() const { return false; }
virtual bool is_unbound_aggregate() const { return false; }
virtual bool is_bound_aggregate() const { return false; }
};
/// \brief An Expression that is always true.
///
/// Represents a boolean predicate that always evaluates to true.
class ICEBERG_EXPORT True : public Expression {
public:
/// \brief Returns the singleton instance
static const std::shared_ptr<True>& Instance();
Operation op() const override { return Operation::kTrue; }
std::string ToString() const override { return "true"; }
Result<std::shared_ptr<Expression>> Negate() const override;
bool Equals(const Expression& other) const override {
return other.op() == Operation::kTrue;
}
private:
constexpr True() = default;
};
/// \brief An expression that is always false.
class ICEBERG_EXPORT False : public Expression {
public:
/// \brief Returns the singleton instance
static const std::shared_ptr<False>& Instance();
Operation op() const override { return Operation::kFalse; }
std::string ToString() const override { return "false"; }
Result<std::shared_ptr<Expression>> Negate() const override;
bool Equals(const Expression& other) const override {
return other.op() == Operation::kFalse;
}
private:
constexpr False() = default;
};
/// \brief An Expression that represents a logical AND operation between two expressions.
///
/// This expression evaluates to true if and only if both of its child expressions
/// evaluate to true.
class ICEBERG_EXPORT And : public Expression {
public:
/// \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
static Result<std::unique_ptr<And>> Make(std::shared_ptr<Expression> left,
std::shared_ptr<Expression> right);
/// \brief Creates a folded And expression from two sub-expressions.
///
/// \param left The left operand of the AND expression
/// \param right The right operand of the AND expression
/// \param args Additional operands of the AND expression
/// \return A Result containing a shared pointer to the folded And expression, or an
/// error if left or right is nullptr
/// \note A folded And expression is an expression that is equivalent to the original
/// expression, but with the And operation removed. For example, (true and x) = x.
template <typename... Args>
static Result<std::shared_ptr<Expression>> MakeFolded(std::shared_ptr<Expression> left,
std::shared_ptr<Expression> right,
Args&&... args)
requires std::conjunction_v<std::is_same<Args, std::shared_ptr<Expression>>...>
{
if constexpr (sizeof...(args) == 0) {
if (left->op() == Expression::Operation::kFalse ||
right->op() == Expression::Operation::kFalse) {
return False::Instance();
}
if (left->op() == Expression::Operation::kTrue) {
return right;
}
if (right->op() == Expression::Operation::kTrue) {
return left;
}
return And::Make(std::move(left), std::move(right));
} else {
ICEBERG_ASSIGN_OR_THROW(auto and_expr,
And::Make(std::move(left), std::move(right)));
return And::MakeFolded(std::move(and_expr), std::forward<Args>(args)...);
}
}
/// \brief Returns the left operand of the AND expression.
///
/// \return The left operand of the AND expression
const std::shared_ptr<Expression>& left() const { return left_; }
/// \brief Returns the right operand of the AND expression.
///
/// \return The right operand of the AND expression
const std::shared_ptr<Expression>& right() const { return right_; }
Operation op() const override { return Operation::kAnd; }
std::string ToString() const override;
Result<std::shared_ptr<Expression>> Negate() const override;
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_;
};
/// \brief An Expression that represents a logical OR operation between two expressions.
///
/// This expression evaluates to true if at least one of its child expressions
/// evaluates to true.
class ICEBERG_EXPORT Or : public Expression {
public:
/// \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
static Result<std::unique_ptr<Or>> Make(std::shared_ptr<Expression> left,
std::shared_ptr<Expression> right);
/// \brief Creates a folded Or expression from two sub-expressions.
///
/// \param left The left operand of the OR expression
/// \param right The right operand of the OR expression
/// \param args Additional operands of the OR expression
/// \return A Result containing a shared pointer to the folded Or expression, or an
/// error if left or right is nullptr
/// \note A folded Or expression is an expression that is equivalent to the original
/// expression, but with the Or operation removed. For example, (false or x) = x.
template <typename... Args>
static Result<std::shared_ptr<Expression>> MakeFolded(std::shared_ptr<Expression> left,
std::shared_ptr<Expression> right,
Args&&... args)
requires std::conjunction_v<std::is_same<Args, std::shared_ptr<Expression>>...>
{
if constexpr (sizeof...(args) == 0) {
if (left->op() == Expression::Operation::kTrue ||
right->op() == Expression::Operation::kTrue) {
return True::Instance();
}
if (left->op() == Expression::Operation::kFalse) {
return right;
}
if (right->op() == Expression::Operation::kFalse) {
return left;
}
return Or::Make(std::move(left), std::move(right));
} else {
ICEBERG_ASSIGN_OR_THROW(auto or_expr, Or::Make(std::move(left), std::move(right)));
return Or::MakeFolded(std::move(or_expr), std::forward<Args>(args)...);
}
}
/// \brief Returns the left operand of the OR expression.
///
/// \return The left operand of the OR expression
const std::shared_ptr<Expression>& left() const { return left_; }
/// \brief Returns the right operand of the OR expression.
///
/// \return The right operand of the OR expression
const std::shared_ptr<Expression>& right() const { return right_; }
Operation op() const override { return Operation::kOr; }
std::string ToString() const override;
Result<std::shared_ptr<Expression>> Negate() const override;
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_;
};
/// \brief An Expression that represents logical NOT operation.
///
/// This expression negates its child expression.
class ICEBERG_EXPORT Not : public Expression {
public:
/// \brief Creates a Not expression from a child expression.
///
/// \param child The expression to negate
/// \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 Creates a folded Not expression from a child expression.
///
/// \param child The expression to negate
/// \return A Result containing a shared pointer to the folded Not expression, or an
/// error if child is nullptr
/// \note A folded Not expression is an expression that is equivalent to the original
/// expression, but with the Not operation removed. For example, not(not(x)) = x.
static Result<std::shared_ptr<Expression>> MakeFolded(
std::shared_ptr<Expression> child);
/// \brief Returns the child expression.
///
/// \return The child expression being negated
const std::shared_ptr<Expression>& child() const { return child_; }
Operation op() const override { return Operation::kNot; }
std::string ToString() const override;
Result<std::shared_ptr<Expression>> Negate() const override;
bool Equals(const Expression& other) const override;
private:
explicit Not(std::shared_ptr<Expression> child);
std::shared_ptr<Expression> child_;
};
/// \brief Returns a string representation of an expression operation.
ICEBERG_EXPORT std::string_view ToString(Expression::Operation op);
/// \brief Returns the negated operation.
ICEBERG_EXPORT Result<Expression::Operation> Negate(Expression::Operation op);
/// \brief Interface for unbound expressions that need schema binding.
///
/// Unbound expressions contain string-based references that must be resolved
/// against a concrete schema to produce bound expressions that can be evaluated.
///
/// \tparam B The bound type this term produces when binding is successful
template <typename B>
class ICEBERG_EXPORT Unbound {
public:
/// \brief Bind this expression to a concrete schema.
///
/// \param schema The schema to bind against
/// \param case_sensitive Whether field name matching should be case sensitive
/// \return A bound expression or an error if binding fails
virtual Result<std::shared_ptr<B>> Bind(const Schema& schema,
bool case_sensitive) const = 0;
/// \brief Overloaded Bind method that uses case-sensitive matching by default.
Result<std::shared_ptr<B>> Bind(const Schema& schema) const;
/// \brief Returns the underlying named reference for this unbound term.
virtual std::shared_ptr<class NamedReference> reference() = 0;
};
/// \brief Interface for bound expressions that can be evaluated.
///
/// Bound expressions have been resolved against a concrete schema and contain
/// all necessary information to evaluate against data structures.
class ICEBERG_EXPORT Bound {
public:
virtual ~Bound();
/// \brief Evaluate this expression against a row-based data.
virtual Result<Literal> Evaluate(const StructLike& data) const = 0;
/// \brief Returns the underlying bound reference for this term.
virtual std::shared_ptr<class BoundReference> reference() = 0;
};
} // namespace iceberg