Skip to main content

parquet/schema/
visitor.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Utilities to traverse against various parquet type.
6
7use crate::basic::{ConvertedType, Repetition};
8use crate::errors::ParquetError::General;
9use crate::errors::Result;
10use crate::schema::types::{Type, TypePtr};
11
12/// A utility trait to help user to traverse against parquet type.
13pub trait TypeVisitor<R, C> {
14    /// Called when a primitive type hit.
15    fn visit_primitive(&mut self, primitive_type: TypePtr, context: C) -> Result<R>;
16
17    /// Default implementation when visiting a list.
18    ///
19    /// It checks list type definition and calls [`Self::visit_list_with_item`] with extracted
20    /// item type.
21    ///
22    /// To fully understand this algorithm, please refer to
23    /// [parquet doc](https://github.com/apache/parquet-format/blob/master/LogicalTypes.md).
24    ///
25    /// For example, a standard list type looks like:
26    ///
27    /// ```text
28    /// required/optional group my_list (LIST) {
29    //    repeated group list {
30    //      required/optional binary element (UTF8);
31    //    }
32    //  }
33    /// ```
34    ///
35    /// In such a case, [`Self::visit_list_with_item`] will be called with `my_list` as the list
36    /// type, and `element` as the `item_type`
37    ///
38    fn visit_list(&mut self, list_type: TypePtr, context: C) -> Result<R> {
39        match list_type.as_ref() {
40            Type::PrimitiveType { .. } => {
41                panic!("{list_type:?} is a list type and must be a group type")
42            }
43            Type::GroupType {
44                basic_info: _,
45                fields,
46            } if fields.len() == 1 => {
47                let list_item = fields.first().unwrap();
48
49                match list_item.as_ref() {
50                    Type::PrimitiveType { .. } => {
51                        if list_item.get_basic_info().repetition() == Repetition::REPEATED {
52                            self.visit_list_with_item(list_type.clone(), list_item.clone(), context)
53                        } else {
54                            Err(General(
55                                "Primitive element type of list must be repeated.".to_string(),
56                            ))
57                        }
58                    }
59                    Type::GroupType {
60                        basic_info: _,
61                        fields,
62                    } => {
63                        if fields.len() == 1
64                            && list_item.name() != "array"
65                            && list_item.name() != format!("{}_tuple", list_type.name())
66                        {
67                            self.visit_list_with_item(
68                                list_type.clone(),
69                                fields.first().unwrap().clone(),
70                                context,
71                            )
72                        } else {
73                            self.visit_list_with_item(list_type.clone(), list_item.clone(), context)
74                        }
75                    }
76                }
77            }
78            Type::GroupType { .. } => Err(General(
79                "Group element type of list can only contain one field.".to_string(),
80            )),
81        }
82    }
83
84    /// Called when a struct type hit.
85    fn visit_struct(&mut self, struct_type: TypePtr, context: C) -> Result<R>;
86
87    /// Called when a map type hit.
88    fn visit_map(&mut self, map_type: TypePtr, context: C) -> Result<R>;
89
90    /// A utility method which detects input type and calls corresponding method.
91    fn dispatch(&mut self, cur_type: TypePtr, context: C) -> Result<R> {
92        if cur_type.is_primitive() {
93            self.visit_primitive(cur_type, context)
94        } else {
95            match cur_type.get_basic_info().converted_type() {
96                ConvertedType::LIST => self.visit_list(cur_type, context),
97                ConvertedType::MAP | ConvertedType::MAP_KEY_VALUE => {
98                    self.visit_map(cur_type, context)
99                }
100                _ => self.visit_struct(cur_type, context),
101            }
102        }
103    }
104
105    /// Called by `visit_list`.
106    fn visit_list_with_item(
107        &mut self,
108        list_type: TypePtr,
109        item_type: TypePtr,
110        context: C,
111    ) -> Result<R>;
112}
113
114#[cfg(test)]
115mod tests {
116    use super::TypeVisitor;
117    use crate::basic::Type as PhysicalType;
118    use crate::errors::Result;
119    use crate::schema::parser::parse_message_type;
120    use crate::schema::types::TypePtr;
121    use std::sync::Arc;
122
123    struct TestVisitorContext {}
124    struct TestVisitor {
125        primitive_visited: bool,
126        struct_visited: bool,
127        list_visited: bool,
128        root_type: TypePtr,
129    }
130
131    impl TypeVisitor<bool, TestVisitorContext> for TestVisitor {
132        fn visit_primitive(
133            &mut self,
134            primitive_type: TypePtr,
135            _context: TestVisitorContext,
136        ) -> Result<bool> {
137            assert_eq!(
138                self.get_field_by_name(primitive_type.name()).as_ref(),
139                primitive_type.as_ref()
140            );
141            self.primitive_visited = true;
142            Ok(true)
143        }
144
145        fn visit_struct(
146            &mut self,
147            struct_type: TypePtr,
148            _context: TestVisitorContext,
149        ) -> Result<bool> {
150            assert_eq!(
151                self.get_field_by_name(struct_type.name()).as_ref(),
152                struct_type.as_ref()
153            );
154            self.struct_visited = true;
155            Ok(true)
156        }
157
158        fn visit_map(&mut self, _map_type: TypePtr, _context: TestVisitorContext) -> Result<bool> {
159            unimplemented!()
160        }
161
162        fn visit_list_with_item(
163            &mut self,
164            list_type: TypePtr,
165            item_type: TypePtr,
166            _context: TestVisitorContext,
167        ) -> Result<bool> {
168            assert_eq!(
169                self.get_field_by_name(list_type.name()).as_ref(),
170                list_type.as_ref()
171            );
172            assert_eq!("element", item_type.name());
173            assert_eq!(PhysicalType::INT32, item_type.get_physical_type());
174            self.list_visited = true;
175            Ok(true)
176        }
177    }
178
179    impl TestVisitor {
180        fn new(root: TypePtr) -> Self {
181            Self {
182                primitive_visited: false,
183                struct_visited: false,
184                list_visited: false,
185                root_type: root,
186            }
187        }
188
189        fn get_field_by_name(&self, name: &str) -> TypePtr {
190            self.root_type
191                .get_fields()
192                .iter()
193                .find(|t| t.name() == name)
194                .cloned()
195                .unwrap()
196        }
197    }
198
199    #[test]
200    fn test_visitor() {
201        let message_type = "
202          message spark_schema {
203            REQUIRED INT32 a;
204            OPTIONAL group inner_schema {
205              REQUIRED INT32 b;
206              REQUIRED DOUBLE c;
207            }
208
209            OPTIONAL group e (LIST) {
210              REPEATED group list {
211                REQUIRED INT32 element;
212              }
213            }
214        ";
215
216        let parquet_type = Arc::new(parse_message_type(message_type).unwrap());
217
218        let mut visitor = TestVisitor::new(parquet_type.clone());
219        for f in parquet_type.get_fields() {
220            let c = TestVisitorContext {};
221            assert!(visitor.dispatch(f.clone(), c).unwrap());
222        }
223
224        assert!(visitor.struct_visited);
225        assert!(visitor.primitive_visited);
226        assert!(visitor.list_visited);
227    }
228}