| /* | |
| * 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/term.h" | |
| #include <format> | |
| #include "iceberg/result.h" | |
| #include "iceberg/row/struct_like.h" | |
| #include "iceberg/schema.h" | |
| #include "iceberg/transform.h" | |
| #include "iceberg/util/checked_cast.h" | |
| #include "iceberg/util/macros.h" | |
| namespace iceberg { | |
| Bound::~Bound() = default; | |
| BoundTerm::~BoundTerm() = default; | |
| Reference::~Reference() = default; | |
| template <typename B> | |
| Result<std::shared_ptr<B>> Unbound<B>::Bind(const Schema& schema) const { | |
| return Bind(schema, /*case_sensitive=*/true); | |
| } | |
| // 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)) { | |
| ICEBERG_DCHECK(!field_name_.empty(), "NamedReference cannot have empty field name"); | |
| } | |
| NamedReference::~NamedReference() = default; | |
| Result<std::shared_ptr<BoundReference>> NamedReference::Bind(const Schema& schema, | |
| bool case_sensitive) const { | |
| ICEBERG_ASSIGN_OR_RAISE(auto field_opt, | |
| schema.FindFieldByName(field_name_, case_sensitive)); | |
| if (!field_opt.has_value()) [[unlikely]] { | |
| return InvalidExpression("Cannot find field '{}' in struct: {}", field_name_, | |
| schema.ToString()); | |
| } | |
| int32_t field_id = field_opt.value().get().field_id(); | |
| ICEBERG_ASSIGN_OR_RAISE(auto accessor, schema.GetAccessorById(field_id)); | |
| return BoundReference::Make(field_opt.value().get(), std::move(accessor)); | |
| } | |
| std::string NamedReference::ToString() const { | |
| return std::format("ref(name=\"{}\")", field_name_); | |
| } | |
| // BoundReference implementation | |
| Result<std::unique_ptr<BoundReference>> BoundReference::Make( | |
| SchemaField field, std::unique_ptr<StructLikeAccessor> accessor) { | |
| if (auto status = field.Validate(); !status.has_value()) [[unlikely]] { | |
| return InvalidExpression("Cannot create BoundReference with invalid field: {}", | |
| status.error().message); | |
| } | |
| if (!accessor) [[unlikely]] { | |
| return InvalidExpression("Cannot create BoundReference without accessor"); | |
| } | |
| return std::unique_ptr<BoundReference>( | |
| new BoundReference(std::move(field), std::move(accessor))); | |
| } | |
| BoundReference::BoundReference(SchemaField field, | |
| std::unique_ptr<StructLikeAccessor> accessor) | |
| : field_(std::move(field)), accessor_(std::move(accessor)) { | |
| ICEBERG_DCHECK(field_.Validate().has_value(), | |
| "Cannot create BoundReference with invalid field"); | |
| ICEBERG_DCHECK(accessor_ != nullptr, "Cannot create BoundReference without accessor"); | |
| } | |
| BoundReference::~BoundReference() = default; | |
| std::string BoundReference::ToString() const { | |
| return std::format("ref(id={}, type={})", field_.field_id(), field_.type()->ToString()); | |
| } | |
| Result<Literal> BoundReference::Evaluate(const StructLike& data) const { | |
| return accessor_->GetLiteral(data); | |
| } | |
| bool BoundReference::Equals(const BoundTerm& other) const { | |
| if (other.kind() != Term::Kind::kReference) { | |
| return false; | |
| } | |
| const auto& other_ref = internal::checked_cast<const BoundReference&>(other); | |
| return field_.field_id() == other_ref.field_.field_id() && | |
| field_.optional() == other_ref.field_.optional() && | |
| *field_.type() == *other_ref.field_.type(); | |
| } | |
| // 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)) { | |
| ICEBERG_DCHECK(!ref || !transform, | |
| "Cannot create UnboundTransform with null reference or transform"); | |
| } | |
| UnboundTransform::~UnboundTransform() = default; | |
| std::string UnboundTransform::ToString() const { | |
| return std::format("{}({})", transform_->ToString(), ref_->ToString()); | |
| } | |
| 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 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)) { | |
| ICEBERG_DCHECK(ref_ && transform_ && transform_func_, | |
| "Cannot create BoundTransform with null reference or transform"); | |
| } | |
| BoundTransform::~BoundTransform() = default; | |
| std::string BoundTransform::ToString() const { | |
| return std::format("{}({})", transform_->ToString(), ref_->ToString()); | |
| } | |
| Result<Literal> BoundTransform::Evaluate(const StructLike& data) const { | |
| ICEBERG_ASSIGN_OR_RAISE(auto literal, ref_->Evaluate(data)); | |
| return transform_func_->Transform(literal); | |
| } | |
| bool BoundTransform::MayProduceNull() const { | |
| // transforms must produce null for null input values | |
| // transforms may produce null for non-null inputs when not order-preserving | |
| return ref_->MayProduceNull() || !transform_->PreservesOrder(); | |
| } | |
| std::shared_ptr<Type> BoundTransform::type() const { | |
| return transform_func_->ResultType(); | |
| } | |
| bool BoundTransform::Equals(const BoundTerm& other) const { | |
| if (other.kind() == Term::Kind::kTransform) { | |
| const auto& other_transform = internal::checked_cast<const BoundTransform&>(other); | |
| return *ref_ == *other_transform.ref_ && *transform_ == *other_transform.transform_; | |
| } | |
| if (transform_->transform_type() == TransformType::kIdentity && | |
| other.kind() == Term::Kind::kReference) { | |
| return *ref_ == other; | |
| } | |
| return false; | |
| } | |
| // Explicit template instantiations | |
| template Result<std::shared_ptr<BoundReference>> Unbound<BoundReference>::Bind( | |
| const Schema& schema) const; | |
| template Result<std::shared_ptr<BoundTransform>> Unbound<BoundTransform>::Bind( | |
| const Schema& schema) const; | |
| } // namespace iceberg |