micromegas_analytics/properties/
properties_to_dict_udf.rs1use datafusion::arrow::array::{
2 Array, AsArray, DictionaryArray, GenericListArray, Int32Array, ListBuilder, StringBuilder,
3 StructArray, StructBuilder,
4};
5use datafusion::arrow::datatypes::{DataType, Field, Fields, Int32Type};
6use datafusion::common::{Result, internal_err};
7use datafusion::error::DataFusionError;
8use datafusion::logical_expr::{
9 ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
10};
11use micromegas_datafusion_extensions::properties::properties_udf::extract_properties_as_vec;
12use std::collections::HashMap;
13use std::sync::Arc;
14
15#[derive(Debug, PartialEq, Eq, Hash)]
16pub struct PropertiesToDict {
17 signature: Signature,
18}
19
20impl PropertiesToDict {
21 pub fn new() -> Self {
22 Self::default()
23 }
24}
25
26impl Default for PropertiesToDict {
27 fn default() -> Self {
28 Self {
29 signature: Signature::exact(
30 vec![DataType::List(Arc::new(Field::new(
31 "Property",
32 DataType::Struct(Fields::from(vec![
33 Field::new("key", DataType::Utf8, false),
34 Field::new("value", DataType::Utf8, false),
35 ])),
36 false,
37 )))],
38 Volatility::Immutable,
39 ),
40 }
41 }
42}
43
44impl ScalarUDFImpl for PropertiesToDict {
45 fn name(&self) -> &str {
46 "properties_to_dict"
47 }
48
49 fn signature(&self) -> &Signature {
50 &self.signature
51 }
52
53 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
54 Ok(DataType::Dictionary(
55 Box::new(DataType::Int32),
56 Box::new(DataType::List(Arc::new(Field::new(
57 "Property",
58 DataType::Struct(Fields::from(vec![
59 Field::new("key", DataType::Utf8, false),
60 Field::new("value", DataType::Utf8, false),
61 ])),
62 false,
63 )))),
64 ))
65 }
66
67 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
68 let args = args.args;
69 if args.len() != 1 {
70 return internal_err!("properties_to_dict expects exactly one argument");
71 }
72
73 match &args[0] {
74 ColumnarValue::Array(array) => {
75 let list_array = array
76 .as_any()
77 .downcast_ref::<GenericListArray<i32>>()
78 .ok_or_else(|| {
79 DataFusionError::Internal(
80 "properties_to_dict requires a list array as input".to_string(),
81 )
82 })?;
83
84 let dict_array = build_dictionary_from_properties(list_array)?;
85 Ok(ColumnarValue::Array(Arc::new(dict_array)))
86 }
87 ColumnarValue::Scalar(_) => {
88 internal_err!("properties_to_dict does not support scalar inputs")
89 }
90 }
91 }
92}
93
94struct PropertiesDictionaryBuilder {
95 map: HashMap<Vec<(String, String)>, usize>,
96 values_builder: ListBuilder<StructBuilder>,
97 keys: Vec<Option<i32>>,
98}
99
100impl PropertiesDictionaryBuilder {
101 fn new(capacity: usize) -> Self {
102 let prop_struct_fields = vec![
103 Field::new("key", DataType::Utf8, false),
104 Field::new("value", DataType::Utf8, false),
105 ];
106 let prop_field = Arc::new(Field::new(
107 "Property",
108 DataType::Struct(Fields::from(prop_struct_fields.clone())),
109 false,
110 ));
111 let values_builder =
112 ListBuilder::new(StructBuilder::from_fields(prop_struct_fields, capacity))
113 .with_field(prop_field);
114
115 Self {
116 map: HashMap::new(),
117 values_builder,
118 keys: Vec::with_capacity(capacity),
119 }
120 }
121
122 fn append_property_list(&mut self, struct_array: &StructArray) -> Result<()> {
123 let prop_vec = extract_properties_as_vec(struct_array)?;
124
125 match self.map.get(&prop_vec) {
126 Some(&index) => {
127 self.keys.push(Some(index as i32));
128 }
129 None => {
130 let new_index = self.map.len();
131 self.add_to_values(&prop_vec)?;
132 self.map.insert(prop_vec, new_index);
133 self.keys.push(Some(new_index as i32));
134 }
135 }
136 Ok(())
137 }
138
139 fn append_null(&mut self) {
140 self.keys.push(None);
141 }
142
143 fn add_to_values(&mut self, properties: &[(String, String)]) -> Result<()> {
144 let struct_builder = self.values_builder.values();
145 for (key, value) in properties {
146 struct_builder
147 .field_builder::<StringBuilder>(0)
148 .ok_or_else(|| DataFusionError::Internal("Failed to get key builder".to_string()))?
149 .append_value(key);
150 struct_builder
151 .field_builder::<StringBuilder>(1)
152 .ok_or_else(|| {
153 DataFusionError::Internal("Failed to get value builder".to_string())
154 })?
155 .append_value(value);
156 struct_builder.append(true);
157 }
158 self.values_builder.append(true);
159 Ok(())
160 }
161
162 fn finish(mut self) -> Result<DictionaryArray<Int32Type>> {
163 let keys = Int32Array::from(self.keys);
164 let values = Arc::new(self.values_builder.finish());
165 DictionaryArray::try_new(keys, values)
166 .map_err(|e| DataFusionError::ArrowError(Box::new(e), None))
167 }
168}
169
170pub fn build_dictionary_from_properties(
171 list_array: &GenericListArray<i32>,
172) -> Result<DictionaryArray<Int32Type>> {
173 let mut builder = PropertiesDictionaryBuilder::new(list_array.len());
174 for i in 0..list_array.len() {
175 if list_array.is_null(i) {
176 builder.append_null();
177 } else {
178 let start = list_array.value_offsets()[i] as usize;
179 let end = list_array.value_offsets()[i + 1] as usize;
180 let sliced_values = list_array.values().slice(start, end - start);
181 let struct_array = sliced_values.as_struct();
182 builder.append_property_list(struct_array)?;
183 }
184 }
185
186 builder.finish()
187}