blob: 16717499f39c80518bc3987111bf25d45b1dfc5f [file]
/*
* 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/util/iterator.h
/// \brief Pull-based iterator interface for fallible, lazily produced values.
#include <deque>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
#include "iceberg/result.h"
namespace iceberg {
/// \brief A pull-based iterator whose reads may fail.
///
/// Iterator implementations own any resources needed to produce values. Destroying an
/// iterator releases those resources, including when iteration stops before reaching the
/// end. Iterators are not thread-safe unless an implementation explicitly says otherwise.
/// Once Next() returns an error or std::nullopt, the iterator is terminal. Subsequent
/// calls return the same terminal result without invoking the implementation again.
///
/// \tparam T Value returned by the iterator.
template <typename T>
class Iterator {
public:
virtual ~Iterator() = default;
Iterator() = default;
Iterator(const Iterator&) = delete;
Iterator& operator=(const Iterator&) = delete;
Iterator(Iterator&&) noexcept = default;
Iterator& operator=(Iterator&&) noexcept = default;
/// \brief Return the next value, or std::nullopt when the iterator is exhausted.
///
/// After this method returns an error or std::nullopt, subsequent calls return the same
/// terminal result without invoking NextImpl().
Result<std::optional<T>> Next() {
if (error_.has_value()) {
return std::unexpected(*error_);
}
if (finished_) {
return std::nullopt;
}
auto result = NextImpl();
if (!result.has_value()) {
error_ = result.error();
} else if (!result.value().has_value()) {
finished_ = true;
}
return result;
}
/// \brief Consume the remaining values into a vector.
Result<std::vector<T>> ToVector() {
auto collect = [this](auto& values, auto append,
auto finish) -> Result<std::vector<T>> {
while (true) {
auto result = Next();
if (!result.has_value()) {
return std::unexpected(std::move(result.error()));
}
auto& value = result.value();
if (!value.has_value()) {
return finish(values);
}
append(values, value.value());
}
};
if constexpr (!std::is_move_constructible_v<T>) {
static_assert(std::is_copy_constructible_v<T>,
"Iterator::ToVector requires T to be move- or copy-constructible");
// For strictly copy-only T, collecting directly into a vector can repeatedly copy
// previously collected elements during vector growth. Stage values in a deque,
// then copy once into an exactly sized vector.
std::deque<T> values;
return collect(
values, [](auto& destination, const T& value) { destination.push_back(value); },
[](const auto& source) {
return std::vector<T>(source.cbegin(), source.cend());
});
} else {
std::vector<T> values;
return collect(
values,
[](auto& destination, T& value) {
destination.push_back(std::move_if_noexcept(value));
},
[](auto& source) { return std::move(source); });
}
}
protected:
/// \brief Produce the next value for Next().
///
/// Implementations must return std::nullopt when exhausted. Next() makes the
/// terminal state sticky, so implementations are not called after exhaustion or error.
virtual Result<std::optional<T>> NextImpl() = 0;
private:
bool finished_ = false;
std::optional<Error> error_;
};
} // namespace iceberg