Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/iceberg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ set(ICEBERG_INCLUDES "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src>"
set(ICEBERG_SOURCES
arrow_c_data_guard_internal.cc
catalog/memory/in_memory_catalog.cc
expression/binder.cc
expression/expression.cc
expression/expressions.cc
expression/literal.cc
expression/predicate.cc
expression/rewrite_not.cc
expression/term.cc
file_reader.cc
file_writer.cc
Expand Down
95 changes: 95 additions & 0 deletions src/iceberg/expression/binder.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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.
*/

#include "iceberg/expression/binder.h"

namespace iceberg {

Binder::Binder(const Schema& schema, bool case_sensitive)
: schema_(schema), case_sensitive_(case_sensitive) {}

Result<std::shared_ptr<Expression>> Binder::Bind(const Schema& schema,
const std::shared_ptr<Expression>& expr,
bool case_sensitive) {
Binder binder(schema, case_sensitive);
return Visit<std::shared_ptr<Expression>, Binder>(expr, binder);
}

Result<std::shared_ptr<Expression>> Binder::AlwaysTrue() { return True::Instance(); }

Result<std::shared_ptr<Expression>> Binder::AlwaysFalse() { return False::Instance(); }

Result<std::shared_ptr<Expression>> Binder::Not(
const std::shared_ptr<Expression>& child_result) {
return iceberg::Not::MakeFolded(child_result);
}

Result<std::shared_ptr<Expression>> Binder::And(
const std::shared_ptr<Expression>& left_result,
const std::shared_ptr<Expression>& right_result) {
return iceberg::And::MakeFolded(left_result, right_result);
}

Result<std::shared_ptr<Expression>> Binder::Or(
const std::shared_ptr<Expression>& left_result,
const std::shared_ptr<Expression>& right_result) {
return iceberg::Or::MakeFolded(left_result, right_result);
}

Result<std::shared_ptr<Expression>> Binder::Predicate(
const std::shared_ptr<UnboundPredicate>& pred) {
ICEBERG_DCHECK(pred != nullptr, "Predicate cannot be null");
return pred->Bind(schema_, case_sensitive_);
}

Result<std::shared_ptr<Expression>> Binder::Predicate(
const std::shared_ptr<BoundPredicate>& pred) {
ICEBERG_DCHECK(pred != nullptr, "Predicate cannot be null");
return InvalidExpression("Found already bound predicate: {}", pred->ToString());
}

Result<bool> IsBoundVisitor::IsBound(const std::shared_ptr<Expression>& expr) {
ICEBERG_DCHECK(expr != nullptr, "Expression cannot be null");
IsBoundVisitor visitor;
return Visit<bool, IsBoundVisitor>(expr, visitor);
}

Result<bool> IsBoundVisitor::AlwaysTrue() { return true; }

Result<bool> IsBoundVisitor::AlwaysFalse() { return true; }

Result<bool> IsBoundVisitor::Not(bool child_result) { return child_result; }

Result<bool> IsBoundVisitor::And(bool left_result, bool right_result) {
return left_result && right_result;
}

Result<bool> IsBoundVisitor::Or(bool left_result, bool right_result) {
return left_result && right_result;
}

Result<bool> IsBoundVisitor::Predicate(const std::shared_ptr<BoundPredicate>& pred) {
return true;
}

Result<bool> IsBoundVisitor::Predicate(const std::shared_ptr<UnboundPredicate>& pred) {
return false;
}

} // namespace iceberg
72 changes: 72 additions & 0 deletions src/iceberg/expression/binder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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/binder.h
/// Bind an expression to a schema.

#include "iceberg/expression/expression_visitor.h"

namespace iceberg {

class ICEBERG_EXPORT Binder : public ExpressionVisitor<std::shared_ptr<Expression>> {
public:
Binder(const Schema& schema, bool case_sensitive);

static Result<std::shared_ptr<Expression>> Bind(const Schema& schema,
const std::shared_ptr<Expression>& expr,
bool case_sensitive);

Result<std::shared_ptr<Expression>> AlwaysTrue() override;
Result<std::shared_ptr<Expression>> AlwaysFalse() override;
Result<std::shared_ptr<Expression>> Not(
const std::shared_ptr<Expression>& child_result) override;
Result<std::shared_ptr<Expression>> And(
const std::shared_ptr<Expression>& left_result,
const std::shared_ptr<Expression>& right_result) override;
Result<std::shared_ptr<Expression>> Or(
const std::shared_ptr<Expression>& left_result,
const std::shared_ptr<Expression>& right_result) override;
Result<std::shared_ptr<Expression>> Predicate(
const std::shared_ptr<BoundPredicate>& pred) override;
Result<std::shared_ptr<Expression>> Predicate(
const std::shared_ptr<UnboundPredicate>& pred) override;

private:
const Schema& schema_;
const bool case_sensitive_;
};

class ICEBERG_EXPORT IsBoundVisitor : public ExpressionVisitor<bool> {
public:
static Result<bool> IsBound(const std::shared_ptr<Expression>& expr);

Result<bool> AlwaysTrue() override;
Result<bool> AlwaysFalse() override;
Result<bool> Not(bool child_result) override;
Result<bool> And(bool left_result, bool right_result) override;
Result<bool> Or(bool left_result, bool right_result) override;
Result<bool> Predicate(const std::shared_ptr<BoundPredicate>& pred) override;
Result<bool> Predicate(const std::shared_ptr<UnboundPredicate>& pred) override;
};

// TODO(gangwu): add the Java parity `ReferenceVisitor`

} // namespace iceberg
18 changes: 18 additions & 0 deletions src/iceberg/expression/expression.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ Result<std::unique_ptr<Not>> Not::Make(std::shared_ptr<Expression> child) {
return std::unique_ptr<Not>(new Not(std::move(child)));
}

Result<std::shared_ptr<Expression>> Not::MakeFolded(std::shared_ptr<Expression> child) {
if (child->op() == Expression::Operation::kTrue) {
return False::Instance();
}

if (child->op() == Expression::Operation::kFalse) {
return True::Instance();
}

// not(not(x)) = x
if (child->op() == Expression::Operation::kNot) {
const auto& not_expr = internal::checked_cast<const ::iceberg::Not&>(*child);
return not_expr.child();
}

return ::iceberg::Not::Make(std::move(child));
}

Not::Not(std::shared_ptr<Expression> child) : child_(std::move(child)) {
ICEBERG_DCHECK(child_ != nullptr, "Not expression cannot have null child");
}
Expand Down
93 changes: 93 additions & 0 deletions src/iceberg/expression/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"
#include "iceberg/util/formattable.h"
#include "iceberg/util/macros.h"

namespace iceberg {

Expand Down Expand Up @@ -80,6 +81,11 @@ class ICEBERG_EXPORT Expression : public util::Formattable {
}

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.
Expand Down Expand Up @@ -137,6 +143,45 @@ class ICEBERG_EXPORT And : public 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
/// 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
Expand Down Expand Up @@ -175,6 +220,44 @@ class ICEBERG_EXPORT Or : public 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
/// 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
Expand Down Expand Up @@ -211,6 +294,16 @@ class ICEBERG_EXPORT Not : public Expression {
/// \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
Expand Down
Loading
Loading