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/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ add_iceberg_test(util_test
endian_test.cc
file_io_test.cc
formatter_test.cc
iterator_test.cc
lazy_test.cc
location_util_test.cc
math_util_internal_test.cc
Expand Down
117 changes: 117 additions & 0 deletions src/iceberg/test/iterator_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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/util/iterator.h"

#include <memory>
#include <optional>
#include <type_traits>
#include <vector>

#include <gtest/gtest.h>

#include "iceberg/test/matchers.h"

namespace iceberg {
namespace {

class CopyOnly {
public:
explicit CopyOnly(int value) : value_(value) {}

CopyOnly(const CopyOnly&) = default;
CopyOnly& operator=(const CopyOnly&) = default;
CopyOnly(CopyOnly&&) = delete;
CopyOnly& operator=(CopyOnly&&) = delete;

int value() const { return value_; }

private:
int value_;
};

static_assert(std::is_copy_constructible_v<CopyOnly>);
static_assert(!std::is_move_constructible_v<CopyOnly>);

class CopyOnlyIterator final : public Iterator<CopyOnly> {
public:
Result<std::optional<CopyOnly>> Next() override {
if (next_ == 3) {
return Result<std::optional<CopyOnly>>(std::in_place, std::nullopt);
}
return Result<std::optional<CopyOnly>>(std::in_place, std::in_place, next_++);
}

private:
int next_ = 0;
};

class MoveOnlyIterator final : public Iterator<std::unique_ptr<int>> {
public:
Result<std::optional<std::unique_ptr<int>>> Next() override {
if (next_ == 3) {
return Result<std::optional<std::unique_ptr<int>>>(std::in_place,
std::nullopt);
}
return Result<std::optional<std::unique_ptr<int>>>(
std::in_place, std::in_place, std::make_unique<int>(next_++));
}

private:
int next_ = 0;
};

class FailingIterator final : public Iterator<int> {
public:
Result<std::optional<int>> Next() override { return Invalid("iteration failed"); }
};

TEST(IteratorTest, ToVectorSupportsCopyOnlyValues) {
CopyOnlyIterator iterator;

ICEBERG_UNWRAP_OR_FAIL(auto values, iterator.ToVector());

ASSERT_EQ(values.size(), 3);
EXPECT_EQ(values[0].value(), 0);
EXPECT_EQ(values[1].value(), 1);
EXPECT_EQ(values[2].value(), 2);
}

TEST(IteratorTest, ToVectorSupportsMoveOnlyValues) {
MoveOnlyIterator iterator;

ICEBERG_UNWRAP_OR_FAIL(auto values, iterator.ToVector());

ASSERT_EQ(values.size(), 3);
EXPECT_EQ(*values[0], 0);
EXPECT_EQ(*values[1], 1);
EXPECT_EQ(*values[2], 2);
}

TEST(IteratorTest, ToVectorPropagatesErrors) {
FailingIterator iterator;

auto result = iterator.ToVector();

EXPECT_THAT(result, IsError(ErrorKind::kInvalid));
EXPECT_THAT(result, HasErrorMessage("iteration failed"));
}
Comment thread
manuzhang marked this conversation as resolved.
Outdated

} // namespace
} // namespace iceberg
1 change: 1 addition & 0 deletions src/iceberg/test/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ iceberg_tests = {
'executor_util_test.cc',
'file_io_test.cc',
'formatter_test.cc',
'iterator_test.cc',
'lazy_test.cc',
'location_util_test.cc',
'math_util_internal_test.cc',
Expand Down
2 changes: 2 additions & 0 deletions src/iceberg/type_fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ struct SessionContext;

/// \brief Task execution.
class Executor;
template <typename T>
class Iterator;

/// \brief Metrics reporting.
class MetricsReporter;
Expand Down
93 changes: 93 additions & 0 deletions src/iceberg/util/iterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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"
#include "iceberg/util/macros.h"
Comment thread
manuzhang marked this conversation as resolved.
Outdated

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.
///
/// \tparam T Value returned by the iterator.
template <typename T>
class Iterator {
public:
virtual ~Iterator() = default;

Iterator() = default;
Iterator(const Iterator&) = delete;
Comment thread
manuzhang marked this conversation as resolved.
Iterator& operator=(const Iterator&) = delete;

/// \brief Return the next value, or std::nullopt when the iterator is exhausted.
virtual Result<std::optional<T>> Next() = 0;
Comment thread
manuzhang marked this conversation as resolved.
Outdated

/// \brief Consume the remaining values into a vector.
Result<std::vector<T>> ToVector() {
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");

// A vector cannot grow portably when T has an explicitly deleted move
// constructor. Stage copy-only values in a deque, then use vector's
// forward-range constructor to allocate the final storage once.
Comment thread
manuzhang marked this conversation as resolved.
Outdated
std::deque<T> values;
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 std::vector<T>(values.cbegin(), values.cend());
}
values.push_back(value.value());
}
} else {
Comment thread
Copilot marked this conversation as resolved.
std::vector<T> values;
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 values;
}
values.push_back(std::move_if_noexcept(value.value()));
}
}
}
Comment thread
Copilot marked this conversation as resolved.
};

} // namespace iceberg
1 change: 1 addition & 0 deletions src/iceberg/util/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ install_headers(
'formatter.h',
'functional.h',
'int128.h',
'iterator.h',
'lazy.h',
'location_util.h',
'macros.h',
Expand Down
Loading