Coverage Report

Created: 2026-07-07 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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