/src/duckdb/extension/parquet/reader/variant_column_reader.cpp
Line | Count | Source |
1 | | #include <stdint.h> |
2 | | #include <algorithm> |
3 | | #include <string> |
4 | | #include <utility> |
5 | | #include <vector> |
6 | | |
7 | | #include "duckdb/common/vector/struct_vector.hpp" |
8 | | #include "reader/variant_column_reader.hpp" |
9 | | #include "reader/variant/parquet_variant_iterator.hpp" |
10 | | #include "column_reader.hpp" |
11 | | #include "duckdb/common/assert.hpp" |
12 | | #include "duckdb/common/constants.hpp" |
13 | | #include "duckdb/common/exception.hpp" |
14 | | #include "duckdb/common/optional_idx.hpp" |
15 | | #include "duckdb/common/optional_ptr.hpp" |
16 | | #include "duckdb/common/typedefs.hpp" |
17 | | #include "duckdb/common/types.hpp" |
18 | | #include "duckdb/common/types/vector.hpp" |
19 | | #include "duckdb/common/unique_ptr.hpp" |
20 | | #include "duckdb/common/vector.hpp" |
21 | | #include "parquet_column_schema.hpp" |
22 | | |
23 | | namespace duckdb_apache { |
24 | | namespace thrift { |
25 | | namespace protocol { |
26 | | class TProtocol; |
27 | | } // namespace protocol |
28 | | } // namespace thrift |
29 | | } // namespace duckdb_apache |
30 | | namespace duckdb_parquet { |
31 | | class ColumnChunk; |
32 | | } // namespace duckdb_parquet |
33 | | |
34 | | namespace duckdb { |
35 | | class ClientContext; |
36 | | class ParquetReader; |
37 | | class ThriftFileTransport; |
38 | | |
39 | | //===--------------------------------------------------------------------===// |
40 | | // Variant Column Reader |
41 | | //===--------------------------------------------------------------------===// |
42 | | VariantColumnReader::VariantColumnReader(ClientContext &context, const ParquetReader &reader, |
43 | | const ParquetColumnSchema &schema, |
44 | | vector<unique_ptr<ColumnReader>> child_readers_p) |
45 | 0 | : ColumnReader(reader, schema), context(context), child_readers(std::move(child_readers_p)) { |
46 | 0 | D_ASSERT(Type().InternalType() == PhysicalType::STRUCT); |
47 | |
|
48 | 0 | for (auto &child : child_readers) { |
49 | 0 | if (child) { |
50 | 0 | child->SetParent(*this); |
51 | 0 | } |
52 | 0 | } |
53 | |
|
54 | 0 | if (child_readers[0]->Schema().name == "metadata" && child_readers[1]->Schema().name == "value") { |
55 | 0 | metadata_reader_idx = 0; |
56 | 0 | value_reader_idx = 1; |
57 | 0 | } else if (child_readers[1]->Schema().name == "metadata" && child_readers[0]->Schema().name == "value") { |
58 | 0 | metadata_reader_idx = 1; |
59 | 0 | value_reader_idx = 0; |
60 | 0 | } else { |
61 | 0 | throw InternalException("The Variant column must have 'metadata' and 'value' as the first two columns"); |
62 | 0 | } |
63 | 0 | } |
64 | | |
65 | 0 | ColumnReader &VariantColumnReader::GetChildReader(idx_t child_idx) { |
66 | 0 | if (!child_readers[child_idx]) { |
67 | 0 | throw InternalException("VariantColumnReader::GetChildReader(%d) - but this child reader is not set", |
68 | 0 | child_idx); |
69 | 0 | } |
70 | 0 | return *child_readers[child_idx].get(); |
71 | 0 | } |
72 | | |
73 | | void VariantColumnReader::InitializeRead(idx_t row_group_idx_p, const vector<ColumnChunk> &columns, |
74 | 0 | TProtocol &protocol_p) { |
75 | 0 | for (auto &child : child_readers) { |
76 | 0 | if (!child) { |
77 | 0 | continue; |
78 | 0 | } |
79 | 0 | child->InitializeRead(row_group_idx_p, columns, protocol_p); |
80 | 0 | } |
81 | 0 | } |
82 | | |
83 | 0 | static LogicalType GetIntermediateGroupType(optional_ptr<ColumnReader> typed_value) { |
84 | 0 | child_list_t<LogicalType> children; |
85 | 0 | children.emplace_back("value", LogicalType::BLOB); |
86 | 0 | if (typed_value) { |
87 | 0 | children.emplace_back("typed_value", typed_value->Type()); |
88 | 0 | } |
89 | 0 | return LogicalType::STRUCT(std::move(children)); |
90 | 0 | } |
91 | | |
92 | | void VariantColumnReader::PrepareChunk(DataChunk &chunk, idx_t &capacity, const vector<LogicalType> &types, |
93 | 0 | idx_t count) { |
94 | 0 | bool needs_init = chunk.ColumnCount() != types.size() || count > capacity; |
95 | 0 | for (idx_t i = 0; !needs_init && i < types.size(); i++) { |
96 | 0 | needs_init = chunk.data[i].GetType() != types[i]; |
97 | 0 | } |
98 | 0 | if (needs_init) { |
99 | 0 | chunk.Destroy(); |
100 | 0 | chunk.Initialize(context, types, count); |
101 | 0 | capacity = count; |
102 | 0 | } else { |
103 | 0 | chunk.Reset(); |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | 0 | idx_t VariantColumnReader::Read(ColumnReaderInput &input, Vector &result) { |
108 | 0 | if (pending_skips > 0) { |
109 | 0 | throw InternalException("VariantColumnReader cannot have pending skips"); |
110 | 0 | } |
111 | 0 | optional_ptr<ColumnReader> typed_value_reader = child_readers.size() == 3 ? child_readers[2].get() : nullptr; |
112 | |
|
113 | 0 | auto &num_values = input.num_values; |
114 | 0 | auto &define_out = input.define_out; |
115 | 0 | auto &repeat_out = input.repeat_out; |
116 | | |
117 | | // If the child reader values are all valid, "define_out" may not be initialized at all |
118 | | // So, we just initialize them to all be valid beforehand |
119 | 0 | std::fill_n(define_out, num_values, MaxDefine()); |
120 | |
|
121 | 0 | auto group_type = GetIntermediateGroupType(typed_value_reader); |
122 | 0 | PrepareChunk(intermediate_chunk, intermediate_capacity, {LogicalType::BLOB, group_type}, num_values); |
123 | 0 | auto &metadata_intermediate = intermediate_chunk.data[0]; |
124 | 0 | auto &intermediate_group = intermediate_chunk.data[1]; |
125 | 0 | auto &group_entries = StructVector::GetEntries(intermediate_group); |
126 | 0 | auto &value_intermediate = group_entries[0]; |
127 | |
|
128 | 0 | ColumnReaderInput metadata_reader_input(num_values, define_out, repeat_out); |
129 | 0 | auto metadata_values = child_readers[metadata_reader_idx]->Read(metadata_reader_input, metadata_intermediate); |
130 | |
|
131 | 0 | ColumnReaderInput value_reader_input(num_values, define_out, repeat_out); |
132 | 0 | auto value_values = child_readers[value_reader_idx]->Read(value_reader_input, value_intermediate); |
133 | |
|
134 | 0 | D_ASSERT(child_readers[metadata_reader_idx]->Schema().name == "metadata"); |
135 | 0 | D_ASSERT(child_readers[value_reader_idx]->Schema().name == "value"); |
136 | |
|
137 | 0 | if (metadata_values != value_values) { |
138 | 0 | throw InvalidInputException( |
139 | 0 | "The Variant column did not contain the same amount of values for 'metadata' and 'value'"); |
140 | 0 | } |
141 | | |
142 | 0 | if (typed_value_reader) { |
143 | 0 | ColumnReaderInput child_input(num_values, define_out, repeat_out); |
144 | 0 | auto typed_values = typed_value_reader->Read(child_input, group_entries[1]); |
145 | 0 | if (typed_values != value_values) { |
146 | 0 | throw InvalidInputException( |
147 | 0 | "The shredded Variant column did not contain the same amount of values for 'typed_value' and 'value'"); |
148 | 0 | } |
149 | 0 | } |
150 | | // convert the actual columns |
151 | 0 | Convert(metadata_intermediate, intermediate_group, result, num_values); |
152 | |
|
153 | 0 | return value_values; |
154 | 0 | } |
155 | | |
156 | 0 | void VariantColumnReader::Skip(idx_t num_values) { |
157 | 0 | for (auto &child : child_readers) { |
158 | 0 | if (!child) { |
159 | 0 | continue; |
160 | 0 | } |
161 | 0 | child->Skip(num_values); |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | 0 | void VariantColumnReader::RegisterPrefetch(ThriftFileTransport &transport, bool allow_merge) { |
166 | 0 | for (auto &child : child_readers) { |
167 | 0 | if (!child) { |
168 | 0 | continue; |
169 | 0 | } |
170 | 0 | child->RegisterPrefetch(transport, allow_merge); |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | 0 | uint64_t VariantColumnReader::TotalCompressedSize() { |
175 | 0 | uint64_t size = 0; |
176 | 0 | for (auto &child : child_readers) { |
177 | 0 | if (!child) { |
178 | 0 | continue; |
179 | 0 | } |
180 | 0 | size += child->TotalCompressedSize(); |
181 | 0 | } |
182 | 0 | return size; |
183 | 0 | } |
184 | | |
185 | 0 | idx_t VariantColumnReader::GroupRowsAvailable() { |
186 | 0 | for (auto &child : child_readers) { |
187 | 0 | if (!child) { |
188 | 0 | continue; |
189 | 0 | } |
190 | 0 | return child->GroupRowsAvailable(); |
191 | 0 | } |
192 | 0 | throw InternalException("No projected columns in struct?"); |
193 | 0 | } |
194 | | |
195 | 0 | bool VariantColumnReader::TypedValueLayoutToType(const LogicalType &typed_value, LogicalType &output) { |
196 | 0 | if (!typed_value.IsNested()) { |
197 | 0 | output = typed_value; |
198 | 0 | return true; |
199 | 0 | } |
200 | 0 | auto type_id = typed_value.id(); |
201 | 0 | if (type_id == LogicalTypeId::STRUCT) { |
202 | | //! OBJECT (...) |
203 | 0 | auto &object_fields = StructType::GetChildTypes(typed_value); |
204 | 0 | child_list_t<LogicalType> children; |
205 | 0 | for (auto &object_field : object_fields) { |
206 | 0 | auto &name = object_field.first; |
207 | 0 | auto &field = object_field.second; |
208 | | //! <name>: { |
209 | | //! value: BLOB, |
210 | | //! typed_value: <type> |
211 | | //! } |
212 | 0 | auto &field_children = StructType::GetChildTypes(field); |
213 | 0 | idx_t index = DConstants::INVALID_INDEX; |
214 | 0 | for (idx_t i = 0; i < field_children.size(); i++) { |
215 | 0 | if (field_children[i].first == "typed_value") { |
216 | 0 | index = i; |
217 | 0 | break; |
218 | 0 | } |
219 | 0 | } |
220 | 0 | if (index == DConstants::INVALID_INDEX) { |
221 | | //! FIXME: we might be able to just omit this field from the OBJECT, instead of flat-out failing the |
222 | | //! conversion No 'typed_value' field, so we can't assign a structured type to this field at all |
223 | 0 | return false; |
224 | 0 | } |
225 | 0 | LogicalType child_type; |
226 | 0 | if (!TypedValueLayoutToType(field_children[index].second, child_type)) { |
227 | 0 | return false; |
228 | 0 | } |
229 | 0 | children.emplace_back(name, child_type); |
230 | 0 | } |
231 | 0 | output = LogicalType::STRUCT(std::move(children)); |
232 | 0 | return true; |
233 | 0 | } |
234 | 0 | if (type_id == LogicalTypeId::LIST) { |
235 | | //! ARRAY |
236 | 0 | auto &element = ListType::GetChildType(typed_value); |
237 | | //! element: { |
238 | | //! value: BLOB, |
239 | | //! typed_value: <type> |
240 | | //! } |
241 | 0 | auto &element_children = StructType::GetChildTypes(element); |
242 | 0 | idx_t index = DConstants::INVALID_INDEX; |
243 | 0 | for (idx_t i = 0; i < element_children.size(); i++) { |
244 | 0 | if (element_children[i].first == "typed_value") { |
245 | 0 | index = i; |
246 | 0 | break; |
247 | 0 | } |
248 | 0 | } |
249 | 0 | if (index == DConstants::INVALID_INDEX) { |
250 | | //! This *might* be allowed by the spec, it's hard to reason about.. |
251 | 0 | return false; |
252 | 0 | } |
253 | 0 | LogicalType child_type; |
254 | 0 | if (!TypedValueLayoutToType(element_children[index].second, child_type)) { |
255 | 0 | return false; |
256 | 0 | } |
257 | 0 | output = LogicalType::LIST(child_type); |
258 | 0 | return true; |
259 | 0 | } |
260 | 0 | throw InvalidInputException("VARIANT typed value has to be a primitive/struct/list, not %s", |
261 | 0 | typed_value.ToString()); |
262 | 0 | } |
263 | | |
264 | | } // namespace duckdb |