datafusion_common/scalar/
struct_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//! [`ScalarStructBuilder`] for building [`ScalarValue::Struct`]
19
20use crate::{Result, ScalarValue};
21use arrow::array::{ArrayRef, StructArray};
22use arrow::datatypes::{DataType, Field, FieldRef, Fields};
23use std::sync::Arc;
24
25/// Builder for [`ScalarValue::Struct`].
26///
27/// See examples on [`ScalarValue`]
28#[derive(Debug, Default)]
29pub struct ScalarStructBuilder {
30    fields: Vec<FieldRef>,
31    arrays: Vec<ArrayRef>,
32}
33
34impl ScalarStructBuilder {
35    /// Create a new `ScalarStructBuilder`
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Return a new [`ScalarValue::Struct`] with a single `null` value.
41    ///
42    /// Note this is different from a struct where each of the specified fields
43    /// are null (e.g. `{a: NULL}`)
44    ///
45    /// # Example
46    ///
47    /// ```rust
48    /// # use arrow::datatypes::{DataType, Field};
49    /// # use datafusion_common::scalar::ScalarStructBuilder;
50    /// let fields = vec![Field::new("a", DataType::Int32, false)];
51    /// let sv = ScalarStructBuilder::new_null(fields);
52    /// // Note this is `NULL`, not `{a: NULL}`
53    /// assert_eq!(format!("{sv}"), "NULL");
54    /// ```
55    ///
56    /// To create a struct where the *fields* are null, use `Self::new()` and
57    /// pass null values for each field:
58    ///
59    /// ```rust
60    /// # use arrow::datatypes::{DataType, Field};
61    /// # use datafusion_common::scalar::{ScalarStructBuilder, ScalarValue};
62    /// // make a nullable field
63    /// let field = Field::new("a", DataType::Int32, true);
64    /// // add a null value for the "a" field
65    /// let sv = ScalarStructBuilder::new()
66    ///     .with_scalar(field, ScalarValue::Int32(None))
67    ///     .build()
68    ///     .unwrap();
69    /// // value is not null, but field is
70    /// assert_eq!(format!("{sv}"), "{a:}");
71    /// ```
72    pub fn new_null(fields: impl IntoFields) -> ScalarValue {
73        DataType::Struct(fields.into()).try_into().unwrap()
74    }
75
76    /// Add the specified field and [`ArrayRef`] to the struct.
77    ///
78    /// Note the array should have a single row.
79    pub fn with_array(mut self, field: impl IntoFieldRef, value: ArrayRef) -> Self {
80        self.fields.push(field.into_field_ref());
81        self.arrays.push(value);
82        self
83    }
84
85    /// Add the specified field and `ScalarValue` to the struct.
86    #[expect(clippy::needless_pass_by_value)] // Skip for public API's compatibility
87    pub fn with_scalar(self, field: impl IntoFieldRef, value: ScalarValue) -> Self {
88        // valid scalar value should not fail
89        let array = value.to_array().unwrap();
90        self.with_array(field, array)
91    }
92
93    /// Add a field with the specified name and value to the struct.
94    /// the field is created with the specified data type and as non nullable
95    pub fn with_name_and_scalar(self, name: &str, value: ScalarValue) -> Self {
96        let field = Field::new(name, value.data_type(), false);
97        self.with_scalar(field, value)
98    }
99
100    /// Return a [`ScalarValue::Struct`] with the fields and values added so far
101    ///
102    /// # Errors
103    ///
104    /// If the [`StructArray`] cannot be created (for example if there is a
105    /// mismatch between field types and arrays) or the arrays do not have
106    /// exactly one element.
107    pub fn build(self) -> Result<ScalarValue> {
108        let Self { fields, arrays } = self;
109
110        let struct_array =
111            StructArray::try_new_with_length(Fields::from(fields), arrays, None, 1)?;
112        Ok(ScalarValue::Struct(Arc::new(struct_array)))
113    }
114}
115
116/// Trait for converting a type into a [`FieldRef`]
117///
118/// Used to avoid having to call `clone()` on a `FieldRef` when adding a field to
119/// a `ScalarStructBuilder`.
120///
121/// TODO potentially upstream this to arrow-rs so that we can
122/// use impl `Into<FieldRef>` instead
123pub trait IntoFieldRef {
124    fn into_field_ref(self) -> FieldRef;
125}
126
127impl IntoFieldRef for FieldRef {
128    fn into_field_ref(self) -> FieldRef {
129        self
130    }
131}
132
133impl IntoFieldRef for &FieldRef {
134    fn into_field_ref(self) -> FieldRef {
135        Arc::clone(self)
136    }
137}
138
139impl IntoFieldRef for Field {
140    fn into_field_ref(self) -> FieldRef {
141        FieldRef::new(self)
142    }
143}
144
145/// Trait for converting a type into a [`Fields`]
146///
147/// This avoids to avoid having to call clone() on an Arc'd `Fields` when adding
148/// a field to a `ScalarStructBuilder`
149///
150/// TODO potentially upstream this to arrow-rs so that we can
151/// use impl `Into<Fields>` instead
152pub trait IntoFields {
153    fn into(self) -> Fields;
154}
155
156impl IntoFields for Fields {
157    fn into(self) -> Fields {
158        self
159    }
160}
161
162impl IntoFields for &Fields {
163    fn into(self) -> Fields {
164        self.clone()
165    }
166}
167
168impl IntoFields for Vec<Field> {
169    fn into(self) -> Fields {
170        Fields::from(self)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    // Other cases are tested by doc tests
179    #[test]
180    fn test_empty_struct() {
181        let sv = ScalarStructBuilder::new().build().unwrap();
182        assert_eq!(format!("{sv}"), "{}");
183    }
184}