Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/iceberg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ set(ICEBERG_INCLUDES "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src>"
set(ICEBERG_SOURCES
arrow_c_data_internal.cc
demo.cc
expression.cc
schema.cc
schema_field.cc
schema_internal.cc
Expand Down
2 changes: 2 additions & 0 deletions src/iceberg/error.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ enum class ErrorKind {
kNotImplemented,
kUnknownError,
kNotSupported,
kInvalidExpression,
kInvalidOperatorType,
};

/// \brief Error with a kind and a message.
Expand Down
129 changes: 129 additions & 0 deletions src/iceberg/expression.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* 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.h"

#include <unordered_map>

namespace iceberg {

expected<Expression::Operation, Error> Expression::FromString(
const std::string& operation_type) {
std::string lowercase_op = operation_type;
std::transform(lowercase_op.begin(), lowercase_op.end(), lowercase_op.begin(),
[](unsigned char c) { return std::tolower(c); });

static const std::unordered_map<std::string, Operation> op_map = {
{"true", Operation::kTrue},
{"false", Operation::kFalse},
{"is_null", Operation::kIsNull},
{"not_null", Operation::kNotNull},
{"is_nan", Operation::kIsNan},
{"not_nan", Operation::kNotNan},
{"lt", Operation::kLt},
{"lt_eq", Operation::kLtEq},
{"gt", Operation::kGt},
{"gt_eq", Operation::kGtEq},
{"eq", Operation::kEq},
{"not_eq", Operation::kNotEq},
{"in", Operation::kIn},
{"not_in", Operation::kNotIn},
{"not", Operation::kNot},
{"and", Operation::kAnd},
{"or", Operation::kOr},
{"starts_with", Operation::kStartsWith},
{"not_starts_with", Operation::kNotStartsWith},
{"count", Operation::kCount},
{"count_star", Operation::kCountStar},
{"max", Operation::kMax},
{"min", Operation::kMin}};

auto it = op_map.find(lowercase_op);
if (it == op_map.end()) {
return unexpected<Error>(
{ErrorKind::kInvalidOperatorType, "Unknown operation type: " + operation_type});
}
return it->second;
}

expected<Expression::Operation, Error> Expression::Negate(Operation op) {
switch (op) {
case Operation::kTrue:
return Operation::kFalse;
case Operation::kFalse:
return Operation::kTrue;
case Operation::kIsNull:
return Operation::kNotNull;
case Operation::kNotNull:
return Operation::kIsNull;
case Operation::kIsNan:
return Operation::kNotNan;
case Operation::kNotNan:
return Operation::kIsNan;
case Operation::kLt:
return Operation::kGtEq;
case Operation::kLtEq:
return Operation::kGt;
case Operation::kGt:
return Operation::kLtEq;
case Operation::kGtEq:
return Operation::kLt;
case Operation::kEq:
return Operation::kNotEq;
case Operation::kNotEq:
return Operation::kEq;
case Operation::kIn:
return Operation::kNotIn;
case Operation::kNotIn:
return Operation::kIn;
case Operation::kStartsWith:
return Operation::kNotStartsWith;
case Operation::kNotStartsWith:
return Operation::kStartsWith;
default:
return unexpected<Error>(
{ErrorKind::kInvalidOperatorType, "No negation defined for operation"});
}
}

expected<Expression::Operation, Error> Expression::FlipLR(Operation op) {
switch (op) {
case Operation::kLt:
return Operation::kGt;
case Operation::kLtEq:
return Operation::kGtEq;
case Operation::kGt:
return Operation::kLt;
case Operation::kGtEq:
return Operation::kLtEq;
case Operation::kEq:
return Operation::kEq;
case Operation::kNotEq:
return Operation::kNotEq;
case Operation::kAnd:
return Operation::kAnd;
case Operation::kOr:
return Operation::kOr;
default:
return unexpected<Error>(
{ErrorKind::kInvalidOperatorType, "No left-right flip for operation"});
}
}

} // namespace iceberg
101 changes: 101 additions & 0 deletions src/iceberg/expression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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.h
/// Boolean expression tree for Iceberg table operations.

#include <string>

#include "iceberg/error.h"
#include "iceberg/expected.h"
#include "iceberg/iceberg_export.h"

namespace iceberg {

/// \brief Represents a boolean expression tree.
class ICEBERG_EXPORT Expression {
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,
kCountStar,
kMax,
kMin
};

virtual ~Expression() = default;

/// \brief Returns the operation for an expression node.
[[nodiscard]] virtual Operation Op() const = 0;

/// \brief Returns the negation of this expression, equivalent to not(this).
[[nodiscard]] virtual expected<std::shared_ptr<Expression>, Error> Negate() const {
return unexpected<Error>(
{ErrorKind::kInvalidExpression, "This expression cannot be negated"});
}

/// \brief Returns whether this expression will accept the same values as another.
///
/// If this returns true, the expressions are guaranteed to return the same evaluation
/// for the same input. However, if this returns false the expressions may return the
/// same evaluation for the same input. That is, expressions may be equivalent even if
/// this returns false.
///
/// For best results, rewrite not and bind expressions before calling this method.
///
/// \param other another expression
/// \return true if the expressions are equivalent
[[nodiscard]] virtual bool IsEquivalentTo(const Expression& other) const {
// only bound predicates can be equivalent
return false;
}

/// \brief Convert operation string to enum
static expected<Operation, Error> FromString(const std::string& operation_type);

/// \brief Returns the operation used when this is negated.
static expected<Operation, Error> Negate(Operation op);

/// \brief Returns the equivalent operation when the left and right operands are
/// exchanged.
static expected<Operation, Error> FlipLR(Operation op);
};

} // namespace iceberg
5 changes: 5 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ target_sources(expected_test PRIVATE expected_test.cc)
target_link_libraries(expected_test PRIVATE iceberg_static GTest::gtest_main GTest::gmock)
add_test(NAME expected_test COMMAND expected_test)

add_executable(expression_test)
target_sources(expression_test PRIVATE expression_test.cc)
target_link_libraries(expression_test PRIVATE iceberg_static GTest::gtest_main GTest::gmock)
add_test(NAME expression_test COMMAND expression_test)

if(ICEBERG_BUILD_BUNDLE)
add_executable(avro_test)
target_sources(avro_test PRIVATE avro_test.cc)
Expand Down
Loading