/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 |