blob: a4bd56644577fa27c63fb984ba4c6b8efd754b25 [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.
*/
#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