Skip to main content

parquet_variant_compute/
variant_array_builder.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
18//! [`VariantArrayBuilder`] implementation
19
20use crate::VariantArray;
21use arrow::array::{ArrayRef, BinaryViewArray, BinaryViewBuilder, NullBufferBuilder, StructArray};
22use arrow_schema::{ArrowError, DataType, Field, Fields};
23use parquet_variant::{
24    BuilderSpecificState, ListBuilder, MetadataBuilder, ObjectBuilder, Variant, VariantBuilderExt,
25    VariantMetadata,
26};
27use parquet_variant::{
28    ParentState, ReadOnlyMetadataBuilder, ValueBuilder, WritableMetadataBuilder,
29};
30use std::sync::Arc;
31
32/// A builder for [`VariantArray`]
33///
34/// This builder is used to construct a `VariantArray` and allows APIs for
35/// adding metadata
36///
37/// This builder always creates a `VariantArray` using [`BinaryViewArray`] for both
38/// the metadata and value fields.
39///
40/// # TODO
41/// 1. Support shredding: <https://github.com/apache/arrow-rs/issues/7895>
42///
43/// ## Example:
44/// ```
45/// # use arrow::array::Array;
46/// # use parquet_variant::{Variant, VariantBuilder, VariantBuilderExt};
47/// # use parquet_variant_compute::VariantArrayBuilder;
48/// # use parquet_variant::ShortString;
49/// // Create a new VariantArrayBuilder with a capacity of 100 rows
50/// let mut builder = VariantArrayBuilder::new(100);
51/// // append variant values
52/// builder.append_variant(Variant::from(42));
53/// // append a null row (note not a Variant::Null)
54/// builder.append_null();
55/// // append an object to the builder using VariantBuilderExt methods directly
56/// builder.new_object()
57///   .with_field("foo", "bar")
58///   .finish();
59///
60/// // bulk insert a list of values
61/// // `Option::None` is a null value
62/// builder.extend([None, Some(Variant::from("norm"))]);
63///
64/// // create the final VariantArray
65/// let variant_array = builder.build();
66/// assert_eq!(variant_array.len(), 5);
67/// // // Access the values
68/// // row 1 is not null and is an integer
69/// assert!(!variant_array.is_null(0));
70/// assert_eq!(variant_array.value(0), Variant::from(42i32));
71/// // row 1 is null
72/// assert!(variant_array.is_null(1));
73/// // row 2 is not null and is an object
74/// assert!(!variant_array.is_null(2));
75/// let value = variant_array.value(2);
76/// let obj = value.as_object().expect("expected object");
77/// assert_eq!(obj.get("foo"), Some(Variant::from("bar")));
78/// // row 3 is null
79/// assert!(variant_array.is_null(3));
80/// // row 4 is not null and is a short string
81/// assert!(!variant_array.is_null(4));
82/// let value = variant_array.value(4);
83/// assert_eq!(value, Variant::ShortString(ShortString::try_new("norm").unwrap()));
84/// ```
85#[derive(Debug)]
86pub struct VariantArrayBuilder {
87    /// Nulls
88    nulls: NullBufferBuilder,
89    /// builder for all the metadata
90    metadata_builder: WritableMetadataBuilder,
91    /// ending offset for each serialized metadata dictionary in the buffer
92    metadata_offsets: Vec<usize>,
93    /// builder for values
94    value_builder: ValueBuilder,
95    /// ending offset for each serialized variant value in the buffer
96    value_offsets: Vec<usize>,
97    /// The fields of the final `StructArray`
98    ///
99    /// TODO: 1) Add extension type metadata
100    /// TODO: 2) Add support for shredding
101    fields: Fields,
102}
103
104impl VariantArrayBuilder {
105    pub fn new(row_capacity: usize) -> Self {
106        // The subfields are expected to be non-nullable according to the parquet variant spec.
107        let metadata_field = Field::new("metadata", DataType::BinaryView, false);
108        let value_field = Field::new("value", DataType::BinaryView, false);
109
110        Self {
111            nulls: NullBufferBuilder::new(row_capacity),
112            metadata_builder: WritableMetadataBuilder::default(),
113            metadata_offsets: Vec::with_capacity(row_capacity),
114            value_builder: ValueBuilder::new(),
115            value_offsets: Vec::with_capacity(row_capacity),
116            fields: Fields::from(vec![metadata_field, value_field]),
117        }
118    }
119
120    /// Build the final builder
121    pub fn build(self) -> VariantArray {
122        let Self {
123            mut nulls,
124            metadata_builder,
125            metadata_offsets,
126            value_builder,
127            value_offsets,
128            fields,
129        } = self;
130
131        let metadata_buffer = metadata_builder.into_inner();
132        let metadata_array = binary_view_array_from_buffers(metadata_buffer, metadata_offsets);
133
134        let value_buffer = value_builder.into_inner();
135        let value_array = binary_view_array_from_buffers(value_buffer, value_offsets);
136
137        // The build the final struct array
138        let inner = StructArray::new(
139            fields,
140            vec![
141                Arc::new(metadata_array) as ArrayRef,
142                Arc::new(value_array) as ArrayRef,
143            ],
144            nulls.finish(),
145        );
146        // TODO add arrow extension type metadata
147
148        VariantArray::try_new(&inner).expect("valid VariantArray by construction")
149    }
150
151    /// Appends a null row to the builder.
152    pub fn append_null(&mut self) {
153        self.nulls.append_null();
154        // The subfields are expected to be non-nullable according to the parquet variant spec.
155        self.metadata_offsets.push(self.metadata_builder.offset());
156        self.value_offsets.push(self.value_builder.offset());
157    }
158
159    /// Appends `n` null rows to the builder.
160    pub fn append_nulls(&mut self, n: usize) {
161        self.nulls.append_n_nulls(n);
162        // The subfields are expected to be non-nullable according to the parquet variant spec.
163        let metadata_offset = self.metadata_builder.offset();
164        let value_offset = self.value_builder.offset();
165        self.metadata_offsets
166            .extend(std::iter::repeat_n(metadata_offset, n));
167        self.value_offsets
168            .extend(std::iter::repeat_n(value_offset, n));
169    }
170
171    /// Append the [`Variant`] to the builder as the next row
172    pub fn append_variant(&mut self, variant: Variant) {
173        ValueBuilder::append_variant(self.parent_state(), variant);
174    }
175
176    /// Creates a builder-specific parent state
177    fn parent_state(&mut self) -> ParentState<'_, ArrayBuilderState<'_>> {
178        let state = ArrayBuilderState {
179            metadata_offsets: &mut self.metadata_offsets,
180            value_offsets: &mut self.value_offsets,
181            nulls: &mut self.nulls,
182        };
183
184        ParentState::new(&mut self.value_builder, &mut self.metadata_builder, state)
185    }
186}
187
188impl<'m, 'v> Extend<Option<Variant<'m, 'v>>> for VariantArrayBuilder {
189    fn extend<T: IntoIterator<Item = Option<Variant<'m, 'v>>>>(&mut self, iter: T) {
190        for v in iter {
191            match v {
192                Some(v) => self.append_variant(v),
193                None => self.append_null(),
194            }
195        }
196    }
197}
198
199/// Builder-specific state for array building that manages array-level offsets and nulls. See
200/// [`VariantBuilderExt`] for details.
201#[derive(Debug)]
202pub struct ArrayBuilderState<'a> {
203    metadata_offsets: &'a mut Vec<usize>,
204    value_offsets: &'a mut Vec<usize>,
205    nulls: &'a mut NullBufferBuilder,
206}
207
208// All changes are pending until finalized
209impl BuilderSpecificState for ArrayBuilderState<'_> {
210    fn finish(
211        &mut self,
212        metadata_builder: &mut dyn MetadataBuilder,
213        value_builder: &mut ValueBuilder,
214    ) {
215        self.metadata_offsets.push(metadata_builder.finish());
216        self.value_offsets.push(value_builder.offset());
217        self.nulls.append_non_null();
218    }
219}
220
221impl VariantBuilderExt for VariantArrayBuilder {
222    type State<'a>
223        = ArrayBuilderState<'a>
224    where
225        Self: 'a;
226
227    /// Appending NULL to a variant array produces an actual NULL value
228    fn append_null(&mut self) {
229        self.append_null();
230    }
231
232    fn append_value<'m, 'v>(&mut self, value: impl Into<Variant<'m, 'v>>) {
233        self.append_variant(value.into());
234    }
235
236    fn try_new_list(&mut self) -> Result<ListBuilder<'_, Self::State<'_>>, ArrowError> {
237        Ok(ListBuilder::new(self.parent_state(), false))
238    }
239
240    fn try_new_object(&mut self) -> Result<ObjectBuilder<'_, Self::State<'_>>, ArrowError> {
241        Ok(ObjectBuilder::new(self.parent_state(), false))
242    }
243}
244
245/// A builder for creating only the value column of a [`VariantArray`]
246///
247/// This builder is used when you have existing metadata and only need to build
248/// the value column. It's useful for scenarios like variant unshredding, data
249/// transformation, or filtering where you want to reuse existing metadata.
250///
251/// The builder produces a [`BinaryViewArray`] that can be combined with existing
252/// metadata to create a complete [`VariantArray`].
253///
254/// # Example:
255/// ```
256/// # use arrow::array::Array;
257/// # use parquet_variant::{Variant};
258/// # use parquet_variant_compute::VariantValueArrayBuilder;
259/// // Create a variant value builder for 10 rows
260/// let mut builder = VariantValueArrayBuilder::new(10);
261///
262/// // Append some values with their corresponding metadata, which the
263/// // builder takes advantage of to avoid creating new metadata.
264/// builder.append_value(Variant::from(42));
265/// builder.append_null();
266/// builder.append_value(Variant::from("hello"));
267///
268/// // Build the final value array
269/// let value_array = builder.build().unwrap();
270/// assert_eq!(value_array.len(), 3);
271/// ```
272#[derive(Debug)]
273pub struct VariantValueArrayBuilder {
274    value_builder: ValueBuilder,
275    value_offsets: Vec<usize>,
276    nulls: NullBufferBuilder,
277}
278
279impl VariantValueArrayBuilder {
280    /// Create a new `VariantValueArrayBuilder` with the specified row capacity
281    pub fn new(row_capacity: usize) -> Self {
282        Self {
283            value_builder: ValueBuilder::new(),
284            value_offsets: Vec::with_capacity(row_capacity),
285            nulls: NullBufferBuilder::new(row_capacity),
286        }
287    }
288
289    /// Build the final value array
290    ///
291    /// Returns a [`BinaryViewArray`] containing the serialized variant values.
292    /// This can be combined with existing metadata to create a complete [`VariantArray`].
293    pub fn build(mut self) -> Result<BinaryViewArray, ArrowError> {
294        let value_buffer = self.value_builder.into_inner();
295        let mut array = binary_view_array_from_buffers(value_buffer, self.value_offsets);
296        if let Some(nulls) = self.nulls.finish() {
297            let (views, buffers, _) = array.into_parts();
298            array = BinaryViewArray::try_new(views, buffers, Some(nulls))?;
299        }
300        Ok(array)
301    }
302
303    /// Append a null row to the builder
304    ///
305    /// WARNING: It is only valid to call this method when building the `value` field of a shredded
306    /// variant column (which is nullable). The `value` field of a binary (unshredded) variant
307    /// column is non-nullable, and callers should instead invoke [`Self::append_value`] with
308    /// `Variant::Null`, passing the appropriate metadata value.
309    pub fn append_null(&mut self) {
310        self.value_offsets.push(self.value_builder.offset());
311        self.nulls.append_null();
312    }
313
314    /// Append a variant value with its corresponding metadata
315    ///
316    /// # Arguments
317    /// * `value` - The variant value to append
318    /// * `metadata` - The metadata dictionary for this variant (used for field name resolution)
319    ///
320    /// # Returns
321    /// * `Ok(())` if the value was successfully appended
322    /// * `Err(ArrowError)` if the variant contains field names not found in the metadata
323    ///
324    /// # Example
325    /// ```
326    /// # use parquet_variant::Variant;
327    /// # use parquet_variant_compute::VariantValueArrayBuilder;
328    /// let mut builder = VariantValueArrayBuilder::new(10);
329    /// builder.append_value(Variant::from(42));
330    /// ```
331    pub fn append_value(&mut self, value: Variant<'_, '_>) {
332        // NOTE: Have to clone because the builder consumes `value`
333        self.builder_ext(&value.metadata().clone())
334            .append_value(value);
335    }
336
337    /// Creates a builder-specific parent state.
338    ///
339    /// For example, this can be useful for code that wants to copy a subset of fields from an
340    /// object `value` as a new row of `value_array_builder`:
341    ///
342    /// ```no_run
343    /// # use parquet_variant::{ObjectBuilder, ReadOnlyMetadataBuilder, Variant};
344    /// # use parquet_variant_compute::VariantValueArrayBuilder;
345    /// # let value = Variant::Null;
346    /// # let mut value_array_builder = VariantValueArrayBuilder::new(0);
347    /// # fn should_keep(field_name: &str) -> bool { todo!() };
348    /// let Variant::Object(obj) = value else {
349    ///     panic!("Not a variant object");
350    /// };
351    /// let mut metadata_builder = ReadOnlyMetadataBuilder::new(&obj.metadata);
352    /// let state = value_array_builder.parent_state(&mut metadata_builder);
353    /// let mut object_builder = ObjectBuilder::new(state, false);
354    /// for (field_name, field_value) in obj.iter() {
355    ///     if should_keep(field_name) {
356    ///         object_builder.insert_bytes(field_name, field_value);
357    ///     }
358    /// }
359    ///  object_builder.finish(); // appends the filtered object
360    /// ```
361    pub fn parent_state<'a>(
362        &'a mut self,
363        metadata_builder: &'a mut dyn MetadataBuilder,
364    ) -> ParentState<'a, ValueArrayBuilderState<'a>> {
365        let state = ValueArrayBuilderState {
366            value_offsets: &mut self.value_offsets,
367            nulls: &mut self.nulls,
368        };
369
370        ParentState::new(&mut self.value_builder, metadata_builder, state)
371    }
372
373    /// Creates a thin [`VariantBuilderExt`] wrapper for this builder, which hides the `metadata`
374    /// parameter (similar to the way [`parquet_variant::ObjectFieldBuilder`] hides field names).
375    pub fn builder_ext<'a>(
376        &'a mut self,
377        metadata: &'a VariantMetadata<'a>,
378    ) -> VariantValueArrayBuilderExt<'a> {
379        VariantValueArrayBuilderExt {
380            metadata_builder: ReadOnlyMetadataBuilder::new(metadata),
381            value_builder: self,
382        }
383    }
384}
385
386/// Builder-specific state for array building that manages array-level offsets and nulls. See
387/// [`VariantBuilderExt`] for details.
388#[derive(Debug)]
389pub struct ValueArrayBuilderState<'a> {
390    value_offsets: &'a mut Vec<usize>,
391    nulls: &'a mut NullBufferBuilder,
392}
393
394// All changes are pending until finalized
395impl BuilderSpecificState for ValueArrayBuilderState<'_> {
396    fn finish(
397        &mut self,
398        _metadata_builder: &mut dyn MetadataBuilder,
399        value_builder: &mut ValueBuilder,
400    ) {
401        self.value_offsets.push(value_builder.offset());
402        self.nulls.append_non_null();
403    }
404}
405
406/// A thin [`VariantBuilderExt`] wrapper that hides the short-lived (per-row)
407/// [`ReadOnlyMetadataBuilder`] instances that [`VariantValueArrayBuilder`] requires.
408pub struct VariantValueArrayBuilderExt<'a> {
409    metadata_builder: ReadOnlyMetadataBuilder<'a>,
410    value_builder: &'a mut VariantValueArrayBuilder,
411}
412
413impl<'a> VariantValueArrayBuilderExt<'a> {
414    /// Creates a new instance from a metadata builder and a reference to a variant value builder.
415    pub fn new(
416        metadata_builder: ReadOnlyMetadataBuilder<'a>,
417        value_builder: &'a mut VariantValueArrayBuilder,
418    ) -> Self {
419        Self {
420            metadata_builder,
421            value_builder,
422        }
423    }
424}
425
426impl<'a> VariantBuilderExt for VariantValueArrayBuilderExt<'a> {
427    type State<'b>
428        = ValueArrayBuilderState<'b>
429    where
430        Self: 'b;
431
432    fn append_null(&mut self) {
433        self.value_builder.append_null()
434    }
435
436    fn append_value<'m, 'v>(&mut self, value: impl Into<Variant<'m, 'v>>) {
437        let state = self.value_builder.parent_state(&mut self.metadata_builder);
438        ValueBuilder::append_variant_bytes(state, value.into());
439    }
440
441    fn try_new_list(&mut self) -> Result<ListBuilder<'_, Self::State<'_>>, ArrowError> {
442        let state = self.value_builder.parent_state(&mut self.metadata_builder);
443        Ok(ListBuilder::new(state, false))
444    }
445
446    fn try_new_object(&mut self) -> Result<ObjectBuilder<'_, Self::State<'_>>, ArrowError> {
447        let state = self.value_builder.parent_state(&mut self.metadata_builder);
448        Ok(ObjectBuilder::new(state, false))
449    }
450}
451
452fn binary_view_array_from_buffers(buffer: Vec<u8>, offsets: Vec<usize>) -> BinaryViewArray {
453    // All offsets are less than or equal to the buffer length, so we can safely cast all offsets
454    // inside the loop below, as long as the buffer length fits in u32.
455    u32::try_from(buffer.len()).expect("buffer length should fit in u32");
456
457    let mut builder = BinaryViewBuilder::with_capacity(offsets.len());
458    let block = builder.append_block(buffer.into());
459    // TODO this can be much faster if it creates the views directly during append
460    let mut start = 0;
461    for end in offsets {
462        let end = end as u32; // Safe cast: validated max offset fits in u32 above
463        builder
464            .try_append_view(block, start, end - start)
465            .expect("Failed to append view");
466        start = end;
467    }
468    builder.finish()
469}
470
471#[cfg(test)]
472mod test {
473    use super::*;
474    use arrow::array::Array;
475    use parquet_variant::{ShortString, Variant};
476
477    /// Test that both the metadata and value buffers are non nullable
478    #[test]
479    fn test_variant_array_builder_non_nullable() {
480        let mut builder = VariantArrayBuilder::new(10);
481
482        builder.extend([
483            None, // should not panic
484            Some(Variant::from(42_i32)),
485        ]);
486
487        let variant_array = builder.build();
488
489        assert_eq!(variant_array.len(), 2);
490        assert!(variant_array.is_null(0));
491        assert!(!variant_array.is_null(1));
492        assert_eq!(variant_array.value(1), Variant::from(42i32));
493
494        // the metadata and value fields of non shredded variants should not be null
495        assert!(variant_array.metadata_field().nulls().is_none());
496        assert!(variant_array.value_field().unwrap().nulls().is_none());
497        let DataType::Struct(fields) = variant_array.data_type() else {
498            panic!("Expected VariantArray to have Struct data type");
499        };
500        for field in fields {
501            assert!(
502                !field.is_nullable(),
503                "Field {} should be non-nullable",
504                field.name()
505            );
506        }
507    }
508
509    /// Test using appending variants to the array builder
510    #[test]
511    fn test_variant_array_builder() {
512        let mut builder = VariantArrayBuilder::new(10);
513        builder.append_null(); // should not panic
514        builder.append_variant(Variant::from(42i32));
515
516        // make an object in the next row
517        builder.new_object().with_field("foo", "bar").finish();
518
519        // append a new list
520        builder
521            .new_list()
522            .with_value(Variant::from(1i32))
523            .with_value(Variant::from(2i32))
524            .finish();
525        let variant_array = builder.build();
526
527        assert_eq!(variant_array.len(), 4);
528        assert!(variant_array.is_null(0));
529        assert!(!variant_array.is_null(1));
530        assert_eq!(variant_array.value(1), Variant::from(42i32));
531        assert!(!variant_array.is_null(2));
532        let variant = variant_array.value(2);
533        let variant = variant.as_object().expect("variant to be an object");
534        assert_eq!(variant.get("foo").unwrap(), Variant::from("bar"));
535        assert!(!variant_array.is_null(3));
536        let variant = variant_array.value(3);
537        let list = variant.as_list().expect("variant to be a list");
538        assert_eq!(list.len(), 2);
539    }
540
541    #[test]
542    fn test_variant_array_builder_append_nulls() {
543        let mut builder = VariantArrayBuilder::new(6);
544        builder.append_variant(Variant::from(1i32));
545        builder.append_nulls(0); // should be a no-op
546        builder.append_nulls(3);
547        builder.append_variant(Variant::from(2i32));
548
549        let variant_array = builder.build();
550
551        assert_eq!(variant_array.len(), 5);
552        assert_eq!(variant_array.value(0), Variant::from(1i32));
553        assert!(variant_array.is_null(1));
554        assert!(variant_array.is_null(2));
555        assert!(variant_array.is_null(3));
556        assert_eq!(variant_array.value(4), Variant::from(2i32));
557    }
558
559    #[test]
560    fn test_extend_variant_array_builder() {
561        let mut b = VariantArrayBuilder::new(3);
562        b.extend([None, Some(Variant::Null), Some(Variant::from("norm"))]);
563
564        let variant_array = b.build();
565
566        assert_eq!(variant_array.len(), 3);
567        assert!(variant_array.is_null(0));
568        assert_eq!(variant_array.value(1), Variant::Null);
569        assert_eq!(
570            variant_array.value(2),
571            Variant::ShortString(ShortString::try_new("norm").unwrap())
572        );
573    }
574
575    #[test]
576    fn test_variant_value_array_builder_basic() {
577        let mut builder = VariantValueArrayBuilder::new(10);
578
579        // Append some values
580        builder.append_value(Variant::from(42i32));
581        builder.append_null();
582        builder.append_value(Variant::from("hello"));
583
584        let value_array = builder.build().unwrap();
585        assert_eq!(value_array.len(), 3);
586    }
587
588    #[test]
589    fn test_variant_value_array_builder_with_objects() {
590        // Populate a variant array with objects
591        let mut builder = VariantArrayBuilder::new(3);
592        builder
593            .new_object()
594            .with_field("name", "Alice")
595            .with_field("age", 30i32)
596            .finish();
597
598        builder
599            .new_object()
600            .with_field("name", "Bob")
601            .with_field("age", 42i32)
602            .with_field("city", "Wonderland")
603            .finish();
604
605        builder
606            .new_object()
607            .with_field("name", "Charlie")
608            .with_field("age", 1i32)
609            .finish();
610
611        let array = builder.build();
612
613        // Copy (some of) the objects over to the value array builder
614        //
615        // NOTE: Because we will reuse the metadata column, we cannot reorder rows. We can only
616        // filter or manipulate values within a row.
617        let mut value_builder = VariantValueArrayBuilder::new(3);
618
619        // straight copy
620        value_builder.append_value(array.value(0));
621
622        // filtering fields takes more work because we need to manually create an object builder
623        let value = array.value(1);
624        let mut builder = value_builder.builder_ext(value.metadata());
625        builder
626            .new_object()
627            .with_field("name", value.get_object_field("name").unwrap())
628            .with_field("age", value.get_object_field("age").unwrap())
629            .finish();
630
631        // same bytes, but now nested and duplicated inside a list
632        let value = array.value(2);
633        let mut builder = value_builder.builder_ext(value.metadata());
634        builder
635            .new_list()
636            .with_value(value.clone())
637            .with_value(value.clone())
638            .finish();
639
640        let array2 = VariantArray::from_parts(
641            array.metadata_field().clone(),
642            Some(Arc::new(value_builder.build().unwrap())),
643            None,
644            None,
645        );
646
647        assert_eq!(array2.len(), 3);
648        assert_eq!(array.value(0), array2.value(0));
649
650        assert_eq!(
651            array.value(1).get_object_field("name"),
652            array2.value(1).get_object_field("name")
653        );
654        assert_eq!(
655            array.value(1).get_object_field("age"),
656            array2.value(1).get_object_field("age")
657        );
658
659        assert_eq!(array.value(2), array2.value(2).get_list_element(0).unwrap());
660        assert_eq!(array.value(2), array2.value(2).get_list_element(1).unwrap());
661    }
662}