Skip to main content

parquet_variant/builder/
metadata.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::collections::HashMap;
19
20use arrow_schema::ArrowError;
21use indexmap::IndexSet;
22
23use crate::{VariantMetadata, int_size};
24
25/// Write little-endian integer to buffer
26fn write_offset(buf: &mut Vec<u8>, value: usize, nbytes: u8) {
27    let bytes = value.to_le_bytes();
28    buf.extend_from_slice(&bytes[..nbytes as usize]);
29}
30
31/// A trait for building variant metadata dictionaries, to be used in conjunction with a
32/// [`ValueBuilder`]. The trait provides methods for managing field names and their IDs, as well as
33/// rolling back a failed builder operation that might have created new field ids.
34///
35/// [`ValueBuilder`]: crate::builder::ValueBuilder
36pub trait MetadataBuilder: std::fmt::Debug {
37    /// Attempts to register a field name, returning the corresponding (possibly newly-created)
38    /// field id on success. Attempting to register the same field name twice will _generally_
39    /// produce the same field id both times, but the variant spec does not actually require it.
40    fn try_upsert_field_name(&mut self, field_name: &str) -> Result<u32, ArrowError>;
41
42    /// Retrieves the field name for a given field id, which must be less than
43    /// [`Self::num_field_names`]. Panics if the field id is out of bounds.
44    fn field_name(&self, field_id: usize) -> &str;
45
46    /// Returns the number of field names stored in this metadata builder. Any number less than this
47    /// is a valid field id. The builder can be reverted back to this size later on (discarding any
48    /// newer/higher field ids) by calling [`Self::truncate_field_names`].
49    fn num_field_names(&self) -> usize;
50
51    /// Reverts the field names to a previous size, discarding any newly out of bounds field ids.
52    fn truncate_field_names(&mut self, new_size: usize);
53
54    /// Finishes the current metadata dictionary, returning the new size of the underlying buffer.
55    fn finish(&mut self) -> usize;
56}
57
58impl MetadataBuilder for WritableMetadataBuilder {
59    fn try_upsert_field_name(&mut self, field_name: &str) -> Result<u32, ArrowError> {
60        Ok(self.upsert_field_name(field_name))
61    }
62    fn field_name(&self, field_id: usize) -> &str {
63        self.field_name(field_id)
64    }
65    fn num_field_names(&self) -> usize {
66        self.num_field_names()
67    }
68    fn truncate_field_names(&mut self, new_size: usize) {
69        self.field_names.truncate(new_size)
70    }
71    fn finish(&mut self) -> usize {
72        self.finish()
73    }
74}
75
76/// A metadata builder that cannot register new field names, and merely returns the field id
77/// associated with a known field name. This is useful for variant unshredding operations, where the
78/// metadata column is fixed and -- per variant shredding spec -- already contains all field names
79/// from the typed_value column. It is also useful when projecting a subset of fields from a variant
80/// object value, since the bytes can be copied across directly without re-encoding their field ids.
81///
82/// NOTE: [`Self::finish`] is a no-op. If the intent is to make a copy of the underlying bytes each
83/// time `finish` is called, a different trait impl will be needed.
84#[derive(Debug)]
85pub struct ReadOnlyMetadataBuilder<'m> {
86    metadata: &'m VariantMetadata<'m>,
87    // A cache that tracks field names this builder has already seen, because finding the field id
88    // for a given field name is expensive -- O(n) for a large and unsorted metadata dictionary.
89    // Field names borrowed from a sorted `metadata` bypass this cache; see
90    // `VariantMetadata::borrowed_field_id`.
91    known_field_names: HashMap<&'m str, u32>,
92}
93
94impl<'m> ReadOnlyMetadataBuilder<'m> {
95    /// Creates a new read-only metadata builder from the given metadata dictionary.
96    pub fn new(metadata: &'m VariantMetadata<'m>) -> Self {
97        Self {
98            metadata,
99            known_field_names: HashMap::new(),
100        }
101    }
102}
103
104impl MetadataBuilder for ReadOnlyMetadataBuilder<'_> {
105    fn try_upsert_field_name(&mut self, field_name: &str) -> Result<u32, ArrowError> {
106        // Callers that copy fields out of an object and back into the same metadata dictionary
107        // (unshredding, shredding, and projection all do this) pass field names that are slices of
108        // the dictionary itself. For a sorted dictionary those resolve without hashing or any
109        // string comparison, which matters because this builder is often created per row and so
110        // its `known_field_names` cache would otherwise be populated and discarded without ever
111        // serving a lookup. An unsorted dictionary may hold duplicate entries, so it keeps using
112        // the cache and the name-based search below.
113        if let Some(field_id) = self.metadata.borrowed_field_id(field_name) {
114            return Ok(field_id);
115        }
116
117        if let Some(field_id) = self.known_field_names.get(field_name) {
118            return Ok(*field_id);
119        }
120
121        let Some((field_id, field_name)) = self.metadata.get_entry(field_name) else {
122            return Err(ArrowError::InvalidArgumentError(format!(
123                "Field name '{field_name}' not found in metadata dictionary"
124            )));
125        };
126
127        self.known_field_names.insert(field_name, field_id);
128        Ok(field_id)
129    }
130    fn field_name(&self, field_id: usize) -> &str {
131        &self.metadata[field_id]
132    }
133    fn num_field_names(&self) -> usize {
134        self.metadata.len()
135    }
136    fn truncate_field_names(&mut self, new_size: usize) {
137        debug_assert_eq!(self.metadata.len(), new_size);
138    }
139    fn finish(&mut self) -> usize {
140        self.metadata.bytes.len()
141    }
142}
143
144/// Builder for constructing metadata for [`Variant`] values.
145///
146/// This is used internally by the [`VariantBuilder`] to construct the metadata
147///
148/// You can use an existing `Vec<u8>` as the metadata buffer by using the `from` impl.
149///
150/// [`Variant`]: crate::Variant
151/// [`VariantBuilder`]: crate::VariantBuilder
152#[derive(Default, Debug)]
153pub struct WritableMetadataBuilder {
154    pub(crate) field_names: IndexSet<String>,
155
156    pub(crate) is_sorted: bool,
157
158    /// Output buffer. Metadata is written to the end of this buffer
159    metadata_buffer: Vec<u8>,
160}
161
162impl WritableMetadataBuilder {
163    /// Upsert field name to dictionary, return its ID
164    pub fn upsert_field_name(&mut self, field_name: &str) -> u32 {
165        let (id, new_entry) = self.field_names.insert_full(field_name.to_string());
166
167        if new_entry {
168            let n = self.num_field_names();
169
170            // Dictionary sort order tracking:
171            // - An empty dictionary is unsorted (ambiguous in spec but required by interop tests)
172            // - A single-entry dictionary is trivially sorted
173            // - Otherwise, an already-sorted dictionary becomes unsorted if the new entry breaks order
174            self.is_sorted =
175                n == 1 || self.is_sorted && (self.field_names[n - 2] < self.field_names[n - 1]);
176        }
177
178        id as u32
179    }
180
181    /// The current length of the underlying metadata buffer
182    pub fn offset(&self) -> usize {
183        self.metadata_buffer.len()
184    }
185
186    /// Returns the number of field names stored in the metadata builder.
187    /// Note: this method should be the only place to call `self.field_names.len()`
188    ///
189    /// # Panics
190    ///
191    /// If the number of field names exceeds the maximum allowed value for `u32`.
192    fn num_field_names(&self) -> usize {
193        let n = self.field_names.len();
194        assert!(u32::try_from(n).is_ok());
195
196        n
197    }
198
199    fn field_name(&self, i: usize) -> &str {
200        &self.field_names[i]
201    }
202
203    fn metadata_size(&self) -> usize {
204        self.field_names.iter().map(|k| k.len()).sum()
205    }
206
207    /// Finalizes the metadata dictionary and appends its serialized bytes to the underlying buffer,
208    /// returning the resulting [`Self::offset`]. The builder state is reset and ready to start
209    /// building a new metadata dictionary.
210    pub fn finish(&mut self) -> usize {
211        let nkeys = self.num_field_names();
212
213        // Calculate metadata size
214        let total_dict_size = self.metadata_size();
215
216        let metadata_buffer = &mut self.metadata_buffer;
217        let is_sorted = std::mem::take(&mut self.is_sorted);
218        let field_names = std::mem::take(&mut self.field_names);
219
220        // Determine appropriate offset size based on the larger of dict size or total string size
221        let max_offset = std::cmp::max(total_dict_size, nkeys);
222        let offset_size = int_size(max_offset) as u8;
223
224        let offset_start = 1 + offset_size as usize;
225        let string_start = offset_start + (nkeys + 1) * offset_size as usize;
226        let metadata_size = string_start + total_dict_size;
227
228        metadata_buffer.reserve(metadata_size);
229
230        // Write header: version=1, field names are sorted, with calculated offset_size
231        metadata_buffer.push(0x01 | ((is_sorted as u8) << 4) | ((offset_size - 1) << 6));
232
233        // Write dictionary size
234        write_offset(metadata_buffer, nkeys, offset_size);
235
236        // Write offsets
237        let mut cur_offset = 0;
238        for key in &field_names {
239            write_offset(metadata_buffer, cur_offset, offset_size);
240            cur_offset += key.len();
241        }
242        // Write final offset
243        write_offset(metadata_buffer, cur_offset, offset_size);
244
245        // Write string data
246        for key in field_names {
247            metadata_buffer.extend_from_slice(key.as_bytes());
248        }
249
250        metadata_buffer.len()
251    }
252
253    /// Returns the inner buffer, consuming self without finalizing any in progress metadata.
254    pub fn into_inner(self) -> Vec<u8> {
255        self.metadata_buffer
256    }
257}
258
259impl<S: AsRef<str>> FromIterator<S> for WritableMetadataBuilder {
260    fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
261        let mut this = Self::default();
262        this.extend(iter);
263
264        this
265    }
266}
267
268impl<S: AsRef<str>> Extend<S> for WritableMetadataBuilder {
269    fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {
270        let iter = iter.into_iter();
271        let (min, _) = iter.size_hint();
272
273        self.field_names.reserve(min);
274
275        for field_name in iter {
276            self.upsert_field_name(field_name.as_ref());
277        }
278    }
279}
280
281#[cfg(test)]
282mod test {
283    use crate::{
284        ParentState, ValueBuilder, VariantMetadata,
285        builder::{
286            metadata::{ReadOnlyMetadataBuilder, WritableMetadataBuilder},
287            object::ObjectBuilder,
288        },
289    };
290
291    #[test]
292    fn test_metadata_builder_from_iter() {
293        let metadata = WritableMetadataBuilder::from_iter(vec!["apple", "banana", "cherry"]);
294        assert_eq!(metadata.num_field_names(), 3);
295        assert_eq!(metadata.field_name(0), "apple");
296        assert_eq!(metadata.field_name(1), "banana");
297        assert_eq!(metadata.field_name(2), "cherry");
298        assert!(metadata.is_sorted);
299
300        let metadata = WritableMetadataBuilder::from_iter(["zebra", "apple", "banana"]);
301        assert_eq!(metadata.num_field_names(), 3);
302        assert_eq!(metadata.field_name(0), "zebra");
303        assert_eq!(metadata.field_name(1), "apple");
304        assert_eq!(metadata.field_name(2), "banana");
305        assert!(!metadata.is_sorted);
306
307        let metadata = WritableMetadataBuilder::from_iter(Vec::<&str>::new());
308        assert_eq!(metadata.num_field_names(), 0);
309        assert!(!metadata.is_sorted);
310    }
311
312    #[test]
313    fn test_metadata_builder_extend() {
314        let mut metadata = WritableMetadataBuilder::default();
315        assert_eq!(metadata.num_field_names(), 0);
316        assert!(!metadata.is_sorted);
317
318        metadata.extend(["apple", "cherry"]);
319        assert_eq!(metadata.num_field_names(), 2);
320        assert_eq!(metadata.field_name(0), "apple");
321        assert_eq!(metadata.field_name(1), "cherry");
322        assert!(metadata.is_sorted);
323
324        // extend with more field names that maintain sort order
325        metadata.extend(vec!["dinosaur", "monkey"]);
326        assert_eq!(metadata.num_field_names(), 4);
327        assert_eq!(metadata.field_name(2), "dinosaur");
328        assert_eq!(metadata.field_name(3), "monkey");
329        assert!(metadata.is_sorted);
330
331        // test extending with duplicate field names
332        let initial_count = metadata.num_field_names();
333        metadata.extend(["apple", "monkey"]);
334        assert_eq!(metadata.num_field_names(), initial_count); // No new fields added
335    }
336
337    #[test]
338    fn test_metadata_builder_extend_sort_order() {
339        let mut metadata = WritableMetadataBuilder::default();
340
341        metadata.extend(["middle"]);
342        assert!(metadata.is_sorted);
343
344        metadata.extend(["zebra"]);
345        assert!(metadata.is_sorted);
346
347        // add field that breaks sort order
348        metadata.extend(["apple"]);
349        assert!(!metadata.is_sorted);
350    }
351
352    #[test]
353    fn test_metadata_builder_from_iter_with_string_types() {
354        // &str
355        let metadata = WritableMetadataBuilder::from_iter(["a", "b", "c"]);
356        assert_eq!(metadata.num_field_names(), 3);
357
358        // string
359        let metadata = WritableMetadataBuilder::from_iter(vec![
360            "a".to_string(),
361            "b".to_string(),
362            "c".to_string(),
363        ]);
364        assert_eq!(metadata.num_field_names(), 3);
365
366        // mixed types (anything that implements AsRef<str>)
367        let field_names: Vec<Box<str>> = vec!["a".into(), "b".into(), "c".into()];
368        let metadata = WritableMetadataBuilder::from_iter(field_names);
369        assert_eq!(metadata.num_field_names(), 3);
370    }
371
372    #[test]
373    fn test_read_only_metadata_builder_fails_on_unknown_field() {
374        // Create metadata with only one field
375        let mut default_builder = WritableMetadataBuilder::default();
376        default_builder.upsert_field_name("known_field");
377        default_builder.finish();
378        let metadata_bytes = default_builder.into_inner();
379
380        // Use the metadata to build new variant values
381        let metadata = VariantMetadata::try_new(&metadata_bytes).unwrap();
382        let mut metadata_builder = ReadOnlyMetadataBuilder::new(&metadata);
383        let mut value_builder = ValueBuilder::new();
384
385        {
386            let state = ParentState::variant(&mut value_builder, &mut metadata_builder);
387            let mut obj = ObjectBuilder::new(state, false);
388
389            // This should succeed
390            obj.insert("known_field", "value");
391
392            // This should fail because "unknown_field" is not in the metadata
393            let result = obj.try_insert("unknown_field", "value");
394            assert!(result.is_err());
395            assert!(
396                result
397                    .unwrap_err()
398                    .to_string()
399                    .contains("Field name 'unknown_field' not found")
400            );
401        }
402    }
403}