Coverage Report

Created: 2026-11-11 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/duckdb/src/common/arrow/arrow_format.cpp
Line
Count
Source
1
#include "duckdb/common/arrow/arrow_format.hpp"
2
3
#include "duckdb/common/arrow/arrow_appender.hpp"
4
#include "duckdb/common/arrow/arrow_converter.hpp"
5
#include "duckdb/common/deque.hpp"
6
#include "duckdb/common/exception.hpp"
7
#include "duckdb/common/helper.hpp"
8
#include "duckdb/common/numeric_utils.hpp"
9
#include "duckdb/common/types/data_chunk.hpp"
10
#include "duckdb/function/table/arrow/arrow_duck_schema.hpp"
11
12
namespace duckdb {
13
14
//===--------------------------------------------------------------------===//
15
// Shared exports
16
//===--------------------------------------------------------------------===//
17
namespace {
18
19
//! Also releases a partly built export, whose n_children counts only the children attached so far
20
0
void ReleaseSharedArray(ArrowArray *array) {
21
0
  if (!array || !array->release) {
22
0
    return;
23
0
  }
24
0
  array->release = nullptr;
25
0
  for (int64_t i = 0; i < array->n_children; i++) {
26
0
    auto child = array->children[i];
27
    // A child without a release was moved out and is released by whoever took it
28
0
    if (child->release) {
29
0
      child->release(child);
30
0
    }
31
0
    delete child;
32
0
  }
33
0
  delete[] array->children;
34
0
  if (array->dictionary) {
35
0
    if (array->dictionary->release) {
36
0
      array->dictionary->release(array->dictionary);
37
0
    }
38
0
    delete array->dictionary;
39
0
  }
40
0
  delete static_cast<ArrowArrayOwner *>(array->private_data);
41
0
  array->children = nullptr;
42
0
  array->dictionary = nullptr;
43
0
  array->private_data = nullptr;
44
0
}
45
46
0
ArrowArray *NewSharedNode() {
47
0
  auto node = make_uniq<ArrowArray>();
48
0
  node->release = ReleaseSharedArray;
49
0
  return node.release();
50
0
}
51
52
//! The target's release is installed before the call and each allocation is attached as soon as it is made,
53
//! so a throw leaves nothing that release cannot free
54
0
void FillSharedArray(ArrowArray &target, const ArrowArray &source, const ArrowArrayOwner &owner) {
55
0
  target.private_data = make_uniq<ArrowArrayOwner>(owner).release();
56
0
  target.length = source.length;
57
0
  target.null_count = source.null_count;
58
0
  target.offset = source.offset;
59
0
  target.n_buffers = source.n_buffers;
60
0
  target.buffers = source.buffers;
61
0
  if (source.n_children > 0) {
62
0
    target.children = make_unsafe_uniq_array<ArrowArray *>(NumericCast<idx_t>(source.n_children)).release();
63
0
    for (int64_t i = 0; i < source.n_children; i++) {
64
0
      target.children[i] = NewSharedNode();
65
0
      target.n_children = i + 1;
66
0
      FillSharedArray(*target.children[i], *source.children[i], owner);
67
0
    }
68
0
  }
69
0
  if (source.dictionary) {
70
0
    target.dictionary = NewSharedNode();
71
0
    FillSharedArray(*target.dictionary, *source.dictionary, owner);
72
0
  }
73
0
}
74
75
} // namespace
76
77
ArrowFormatGlobalState::ArrowFormatGlobalState(const ResultFormatContext &context)
78
0
    : types(context.types), properties(context.client_properties) {
79
0
  if (!properties.client_context) {
80
0
    throw InternalException("The Arrow format needs the client context of the query that submitted it");
81
0
  }
82
0
  extension_types = ArrowTypeExtensionData::GetExtensionTypes(*properties.client_context, types);
83
0
  ArrowConverter::ToArrowSchema(&schema.arrow_schema, types, IdentifiersToStrings(context.names), properties);
84
0
}
85
86
0
ArrowFormatGlobalState::~ArrowFormatGlobalState() {
87
0
}
88
89
//===--------------------------------------------------------------------===//
90
// ArrowFormat
91
//===--------------------------------------------------------------------===//
92
namespace {
93
94
class ArrowUnit : public ResultUnit {
95
public:
96
  ArrowUnit(idx_t row_count, idx_t byte_size, unique_ptr<ArrowArrayWrapper> array_p)
97
0
      : ResultUnit(row_count, byte_size), array(std::move(array_p)) {
98
0
  }
99
100
public:
101
  unique_ptr<ArrowArrayWrapper> array;
102
};
103
104
class ArrowFormatLocalState : public ResultFormatLocalState {
105
public:
106
  //! The array being built. Created at the first row that goes into it
107
  unique_ptr<ArrowAppender> appender;
108
  //! Arrays that reached the batch size and have not been handed over yet
109
  deque<unique_ptr<ArrowUnit>> sealed;
110
};
111
112
0
unique_ptr<ArrowUnit> SealAppender(ArrowFormatLocalState &lstate) {
113
  // Finalize hands the buffers to the array, so the size has to be read before it
114
0
  auto rows = lstate.appender->RowCount();
115
0
  auto bytes = lstate.appender->ByteSize();
116
  // Allocated before Finalize, so nothing can throw between the array's creation and its adoption
117
0
  auto array = make_uniq<ArrowArrayWrapper>();
118
0
  array->arrow_array = lstate.appender->Finalize();
119
0
  lstate.appender.reset();
120
0
  return make_uniq<ArrowUnit>(rows, bytes, std::move(array));
121
0
}
122
123
} // namespace
124
125
0
ArrowFormat::ArrowFormat(idx_t batch_size_p) : batch_size(batch_size_p) {
126
0
  if (batch_size == 0) {
127
0
    throw InvalidInputException("The Arrow format needs a batch size of at least one row");
128
0
  }
129
0
}
130
131
0
const char *ArrowFormat::Name() const {
132
0
  return NAME;
133
0
}
134
135
0
unique_ptr<ResultFormatGlobalState> ArrowFormat::InitGlobal(const ResultFormatContext &context) {
136
0
  return make_uniq<ArrowFormatGlobalState>(context);
137
0
}
138
139
0
unique_ptr<ResultFormatLocalState> ArrowFormat::InitLocal(ResultFormatGlobalState &gstate) {
140
0
  return make_uniq<ArrowFormatLocalState>();
141
0
}
142
143
0
void ArrowFormat::AppendToUnit(ResultFormatGlobalState &gstate_p, ResultFormatLocalState &lstate_p, DataChunk &chunk) {
144
0
  auto &gstate = gstate_p.Cast<ArrowFormatGlobalState>();
145
0
  auto &lstate = lstate_p.Cast<ArrowFormatLocalState>();
146
0
  auto count = chunk.size();
147
0
  idx_t processed = 0;
148
0
  while (processed < count) {
149
0
    if (!lstate.appender) {
150
0
      auto capacity = MinValue(batch_size, count - processed);
151
0
      lstate.appender =
152
0
          make_uniq<ArrowAppender>(gstate.Types(), capacity, gstate.Properties(), gstate.ExtensionTypes());
153
0
    }
154
0
    auto to_append = MinValue(batch_size - lstate.appender->RowCount(), count - processed);
155
0
    lstate.appender->Append(chunk, processed, processed + to_append, count);
156
0
    processed += to_append;
157
0
    if (lstate.appender->RowCount() >= batch_size) {
158
0
      lstate.sealed.push_back(SealAppender(lstate));
159
0
    }
160
0
  }
161
0
}
162
163
0
bool ArrowFormat::IsUnitFinished(ResultFormatLocalState &lstate) {
164
0
  return !lstate.Cast<ArrowFormatLocalState>().sealed.empty();
165
0
}
166
167
0
unique_ptr<ResultUnit> ArrowFormat::FinishUnit(ResultFormatGlobalState &gstate, ResultFormatLocalState &lstate_p) {
168
0
  auto &lstate = lstate_p.Cast<ArrowFormatLocalState>();
169
0
  if (!lstate.sealed.empty()) {
170
0
    auto unit = std::move(lstate.sealed.front());
171
0
    lstate.sealed.pop_front();
172
0
    return std::move(unit);
173
0
  }
174
0
  if (!lstate.appender || lstate.appender->RowCount() == 0) {
175
0
    return nullptr;
176
0
  }
177
0
  return SealAppender(lstate);
178
0
}
179
180
unique_ptr<RetainedResultCollection> ArrowFormat::CreateCollection(ClientContext &context,
181
                                                                   ResultFormatGlobalState &gstate,
182
0
                                                                   const ResultFormatContext &format_context) {
183
0
  return make_uniq<ArrowRetainedCollection>(*this, gstate);
184
0
}
185
186
0
unique_ptr<ArrowArrayWrapper> ArrowFormat::UnpackUnit(unique_ptr<ResultUnit> unit) {
187
0
  if (!unit) {
188
0
    return nullptr;
189
0
  }
190
0
  return std::move(unit->Cast<ArrowUnit>().array);
191
0
}
192
193
0
unique_ptr<ArrowArrayWrapper> ArrowFormat::ShareArray(const ArrowArrayOwner &owner) {
194
0
  D_ASSERT(owner && owner->arrow_array.release);
195
0
  auto result = make_uniq<ArrowArrayWrapper>();
196
0
  result->arrow_array = ArrowArray {};
197
0
  result->arrow_array.release = ReleaseSharedArray;
198
0
  FillSharedArray(result->arrow_array, owner->arrow_array, owner);
199
0
  return result;
200
0
}
201
202
0
idx_t ArrowFormat::ChangedRows(const ArrowArray &array) {
203
0
  if (array.length == 0) {
204
0
    return 0;
205
0
  }
206
0
  D_ASSERT(array.length == 1 && array.n_children == 1);
207
0
  auto &count = *array.children[0];
208
0
  auto values = static_cast<const int64_t *>(count.buffers[1]);
209
0
  return NumericCast<idx_t>(values[array.offset + count.offset]);
210
0
}
211
212
//===--------------------------------------------------------------------===//
213
// ArrowRetainedCollection
214
//===--------------------------------------------------------------------===//
215
0
unique_ptr<ArrowArrayWrapper> ArrowRetainedCollection::Fetch() {
216
0
  auto &arrays = Get();
217
0
  if (fetch_index >= arrays.size()) {
218
0
    return nullptr;
219
0
  }
220
0
  auto array = ArrowFormat::ShareArray(arrays[fetch_index]);
221
0
  fetch_index++;
222
0
  return array;
223
0
}
224
225
} // namespace duckdb