Skip to content

Commit b42f0da

Browse files
committed
feat(inspect): add ArrowRowBuilder for materializing Arrow batches
Add ArrowRowBuilder (inspect/row_builder_internal) to materialize in-memory rows into an ArrowArray for an arbitrary Iceberg schema, with typed append helpers (AppendNull/Boolean/Int/String/StringMap) reused by later metadata tables.
1 parent 7fe2e93 commit b42f0da

6 files changed

Lines changed: 446 additions & 1 deletion

File tree

src/iceberg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ set(ICEBERG_SOURCES
4545
file_writer.cc
4646
inspect/history_table.cc
4747
inspect/metadata_table.cc
48+
inspect/row_builder_internal.cc
4849
inspect/snapshots_table.cc
4950
inheritable_metadata.cc
5051
json_serde.cc
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/inspect/row_builder_internal.h"
21+
22+
#include <utility>
23+
24+
#include <nanoarrow/nanoarrow.h>
25+
26+
#include "iceberg/arrow/nanoarrow_status_internal.h"
27+
#include "iceberg/arrow_c_data_guard_internal.h"
28+
#include "iceberg/schema.h"
29+
#include "iceberg/schema_internal.h"
30+
31+
namespace iceberg {
32+
33+
Result<ArrowRowBuilder> ArrowRowBuilder::Make(const Schema& schema) {
34+
ArrowSchema arrow_schema;
35+
ICEBERG_RETURN_UNEXPECTED(ToArrowSchema(schema, &arrow_schema));
36+
internal::ArrowSchemaGuard schema_guard(&arrow_schema);
37+
38+
auto array = std::make_unique<ArrowArray>();
39+
ArrowError error;
40+
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
41+
ArrowArrayInitFromSchema(array.get(), &arrow_schema, &error), error);
42+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayStartAppending(array.get()));
43+
44+
return ArrowRowBuilder(std::move(array));
45+
}
46+
47+
ArrowRowBuilder::ArrowRowBuilder(std::unique_ptr<ArrowArray>&& array) noexcept
48+
: array_(std::move(array)) {}
49+
50+
ArrowRowBuilder::ArrowRowBuilder(ArrowRowBuilder&& other) noexcept
51+
: array_(std::move(other.array_)) {}
52+
53+
ArrowRowBuilder& ArrowRowBuilder::operator=(ArrowRowBuilder&& other) noexcept {
54+
if (this != &other) {
55+
if (array_ != nullptr && array_->release != nullptr) {
56+
ArrowArrayRelease(array_.get());
57+
}
58+
array_ = std::move(other.array_);
59+
}
60+
return *this;
61+
}
62+
63+
ArrowRowBuilder::~ArrowRowBuilder() {
64+
if (array_ != nullptr && array_->release != nullptr) {
65+
ArrowArrayRelease(array_.get());
66+
}
67+
}
68+
69+
int64_t ArrowRowBuilder::num_columns() const {
70+
return array_ == nullptr ? 0 : array_->n_children;
71+
}
72+
73+
ArrowArray* ArrowRowBuilder::column(int64_t index) {
74+
if (array_ == nullptr || index < 0 || index >= array_->n_children) {
75+
return nullptr;
76+
}
77+
return array_->children[index];
78+
}
79+
80+
Status ArrowRowBuilder::FinishRow() {
81+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayFinishElement(array_.get()));
82+
return {};
83+
}
84+
85+
Result<ArrowArray> ArrowRowBuilder::Finish() && {
86+
ArrowError error;
87+
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
88+
ArrowArrayFinishBuildingDefault(array_.get(), &error), error);
89+
ArrowArray result = *array_;
90+
array_->release = nullptr;
91+
return result;
92+
}
93+
94+
Status AppendNull(ArrowArray* array) {
95+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendNull(array, 1));
96+
return {};
97+
}
98+
99+
Status AppendBoolean(ArrowArray* array, bool value) {
100+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendInt(array, value ? 1 : 0));
101+
return {};
102+
}
103+
104+
Status AppendInt(ArrowArray* array, int64_t value) {
105+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendInt(array, value));
106+
return {};
107+
}
108+
109+
Status AppendString(ArrowArray* array, std::string_view value) {
110+
ArrowStringView view(value.data(), static_cast<int64_t>(value.size()));
111+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendString(array, view));
112+
return {};
113+
}
114+
115+
Status AppendStringMap(ArrowArray* array,
116+
const std::unordered_map<std::string, std::string>& entries) {
117+
// A nanoarrow map array is a list of struct<key, value>. children[0] is the
118+
// entries struct, whose children[0]/children[1] are the key/value builders.
119+
ArrowArray* struct_array = array->children[0];
120+
ArrowArray* key_array = struct_array->children[0];
121+
ArrowArray* value_array = struct_array->children[1];
122+
123+
for (const auto& [key, value] : entries) {
124+
ICEBERG_RETURN_UNEXPECTED(AppendString(key_array, key));
125+
ICEBERG_RETURN_UNEXPECTED(AppendString(value_array, value));
126+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayFinishElement(struct_array));
127+
}
128+
129+
// Finish the (possibly empty) map element on the outer list.
130+
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayFinishElement(array));
131+
return {};
132+
}
133+
134+
} // namespace iceberg
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
/// \file iceberg/inspect/row_builder_internal.h
23+
/// Internal Arrow row-building utilities shared by metadata tables.
24+
///
25+
/// Metadata tables (snapshots, history, manifests, ...) materialize in-memory
26+
/// structures into Arrow batches that conform to the table's Iceberg schema.
27+
/// `ArrowRowBuilder` wraps a nanoarrow `ArrowArray` initialized from such a
28+
/// schema and exposes per-column builders plus typed append helpers so each
29+
/// metadata table can emit rows without re-implementing the nanoarrow
30+
/// boilerplate.
31+
32+
#include <cstdint>
33+
#include <memory>
34+
#include <string_view>
35+
#include <unordered_map>
36+
37+
#include "iceberg/arrow_c_data.h"
38+
#include "iceberg/iceberg_export.h"
39+
#include "iceberg/result.h"
40+
#include "iceberg/type_fwd.h"
41+
42+
namespace iceberg {
43+
44+
/// \brief Builds an Arrow struct array (a batch) for an arbitrary Iceberg schema.
45+
///
46+
/// Typical usage:
47+
/// \code
48+
/// ICEBERG_ASSIGN_OR_RAISE(auto builder, ArrowRowBuilder::Make(schema));
49+
/// for (const auto& row : rows) {
50+
/// ICEBERG_RETURN_UNEXPECTED(AppendInt(builder.column(0), row.id));
51+
/// ICEBERG_RETURN_UNEXPECTED(AppendString(builder.column(1), row.name));
52+
/// ICEBERG_RETURN_UNEXPECTED(builder.FinishRow());
53+
/// }
54+
/// ICEBERG_ASSIGN_OR_RAISE(auto array, std::move(builder).Finish());
55+
/// \endcode
56+
class ICEBERG_EXPORT ArrowRowBuilder {
57+
public:
58+
/// \brief Create a row builder for the given Iceberg schema.
59+
static Result<ArrowRowBuilder> Make(const Schema& schema);
60+
61+
ArrowRowBuilder(ArrowRowBuilder&& other) noexcept;
62+
ArrowRowBuilder& operator=(ArrowRowBuilder&& other) noexcept;
63+
64+
ArrowRowBuilder(const ArrowRowBuilder&) = delete;
65+
ArrowRowBuilder& operator=(const ArrowRowBuilder&) = delete;
66+
67+
~ArrowRowBuilder();
68+
69+
/// \brief The number of top-level columns in the batch.
70+
int64_t num_columns() const;
71+
72+
/// \brief Access the nanoarrow child builder for a top-level column.
73+
///
74+
/// \param index Zero-based column index. Returns nullptr if out of range.
75+
ArrowArray* column(int64_t index);
76+
77+
/// \brief Finish the current row, advancing the struct length by one.
78+
///
79+
/// Call after appending exactly one value (or null) to every column.
80+
Status FinishRow();
81+
82+
/// \brief Finish building and transfer ownership of the resulting array.
83+
///
84+
/// The builder must not be used after this call.
85+
Result<ArrowArray> Finish() &&;
86+
87+
private:
88+
explicit ArrowRowBuilder(std::unique_ptr<ArrowArray>&& array) noexcept;
89+
90+
std::unique_ptr<ArrowArray> array_;
91+
};
92+
93+
/// \brief Append a null to a nanoarrow array builder.
94+
ICEBERG_EXPORT Status AppendNull(ArrowArray* array);
95+
96+
/// \brief Append a boolean value to a nanoarrow array builder.
97+
ICEBERG_EXPORT Status AppendBoolean(ArrowArray* array, bool value);
98+
99+
/// \brief Append an integer value to a nanoarrow array builder.
100+
///
101+
/// Works for int32/int64/timestamp columns, which nanoarrow stores as int64.
102+
ICEBERG_EXPORT Status AppendInt(ArrowArray* array, int64_t value);
103+
104+
/// \brief Append a string value to a nanoarrow array builder.
105+
ICEBERG_EXPORT Status AppendString(ArrowArray* array, std::string_view value);
106+
107+
/// \brief Append a map<string, string> value to a nanoarrow map array builder.
108+
///
109+
/// Appends one (possibly empty) map element. The iteration order of the
110+
/// resulting entries is unspecified.
111+
ICEBERG_EXPORT Status AppendStringMap(
112+
ArrowArray* array, const std::unordered_map<std::string, std::string>& entries);
113+
114+
} // namespace iceberg

src/iceberg/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ iceberg_sources = files(
7171
'inheritable_metadata.cc',
7272
'inspect/history_table.cc',
7373
'inspect/metadata_table.cc',
74+
'inspect/row_builder_internal.cc',
7475
'inspect/snapshots_table.cc',
7576
'json_serde.cc',
7677
'location_provider.cc',

src/iceberg/test/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ add_iceberg_test(schema_test
8989
add_iceberg_test(table_test
9090
SOURCES
9191
location_provider_test.cc
92-
metadata_table_test.cc
9392
metrics_config_test.cc
9493
metrics_reporter_test.cc
9594
metrics_test.cc
@@ -185,6 +184,12 @@ if(ICEBERG_BUILD_BUNDLE)
185184

186185
add_iceberg_test(catalog_test USE_BUNDLE SOURCES in_memory_catalog_test.cc)
187186

187+
add_iceberg_test(metadata_table_test
188+
USE_BUNDLE
189+
SOURCES
190+
metadata_table_test.cc
191+
row_builder_test.cc)
192+
188193
add_iceberg_test(eval_expr_test
189194
USE_BUNDLE
190195
SOURCES

0 commit comments

Comments
 (0)