raphtory_api/core/
mod.rs

1use std::{
2    collections::HashMap,
3    fmt::{self, Display, Formatter},
4};
5
6use serde::{Deserialize, Serialize};
7
8pub mod entities;
9pub mod input;
10pub mod storage;
11pub mod utils;
12
13/// Denotes the direction of an edge. Can be incoming, outgoing or both.
14#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug, Default, Serialize, Deserialize)]
15pub enum Direction {
16    OUT,
17    IN,
18    #[default]
19    BOTH,
20}
21
22#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
23pub enum PropType {
24    #[default]
25    Empty,
26    Str,
27    U8,
28    U16,
29    I32,
30    I64,
31    U32,
32    U64,
33    F32,
34    F64,
35    Bool,
36    List(Box<PropType>),
37    Map(HashMap<String, PropType>),
38    NDTime,
39    DTime,
40    Array(Box<PropType>),
41    Decimal {
42        scale: i64,
43    },
44}
45
46impl Display for PropType {
47    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
48        let type_str = match self {
49            PropType::Empty => "Empty",
50            PropType::Str => "Str",
51            PropType::U8 => "U8",
52            PropType::U16 => "U16",
53            PropType::I32 => "I32",
54            PropType::I64 => "I64",
55            PropType::U32 => "U32",
56            PropType::U64 => "U64",
57            PropType::F32 => "F32",
58            PropType::F64 => "F64",
59            PropType::Bool => "Bool",
60            PropType::List(p_type) => return write!(f, "List<{}>", p_type),
61            PropType::Map(p_type) => {
62                let mut types = p_type
63                    .iter()
64                    .map(|(k, v)| format!("{}: {}", k, v))
65                    .collect::<Vec<String>>();
66                types.sort();
67                return write!(f, "Map{{ {} }}", types.join(", "));
68            }
69            PropType::NDTime => "NDTime",
70            PropType::DTime => "DTime",
71            PropType::Array(p_type) => return write!(f, "Array<{}>", p_type),
72            PropType::Decimal { scale } => return write!(f, "Decimal({})", scale),
73        };
74
75        write!(f, "{}", type_str)
76    }
77}
78
79impl PropType {
80    pub fn map(fields: impl IntoIterator<Item = (impl AsRef<str>, PropType)>) -> Self {
81        PropType::Map(
82            fields
83                .into_iter()
84                .map(|(k, v)| (k.as_ref().to_owned(), v))
85                .collect(),
86        )
87    }
88    pub fn is_numeric(&self) -> bool {
89        matches!(
90            self,
91            PropType::U8
92                | PropType::U16
93                | PropType::U32
94                | PropType::U64
95                | PropType::I32
96                | PropType::I64
97                | PropType::F32
98                | PropType::F64
99                | PropType::Decimal { .. }
100        )
101    }
102
103    pub fn is_str(&self) -> bool {
104        matches!(self, PropType::Str)
105    }
106
107    pub fn is_bool(&self) -> bool {
108        matches!(self, PropType::Bool)
109    }
110
111    pub fn is_date(&self) -> bool {
112        matches!(self, PropType::DTime | PropType::NDTime)
113    }
114
115    pub fn has_add(&self) -> bool {
116        self.is_numeric() || self.is_str()
117    }
118
119    pub fn has_divide(&self) -> bool {
120        self.is_numeric()
121    }
122
123    pub fn has_cmp(&self) -> bool {
124        self.is_bool() || self.is_numeric() || self.is_str() || self.is_date()
125    }
126}
127
128use crate::core::entities::properties::PropError;
129#[cfg(feature = "storage")]
130use polars_arrow::datatypes::ArrowDataType as DataType;
131
132#[cfg(feature = "storage")]
133impl From<&DataType> for PropType {
134    fn from(value: &DataType) -> Self {
135        match value {
136            DataType::Utf8 => PropType::Str,
137            DataType::LargeUtf8 => PropType::Str,
138            DataType::UInt8 => PropType::U8,
139            DataType::UInt16 => PropType::U16,
140            DataType::Int32 => PropType::I32,
141            DataType::Int64 => PropType::I64,
142            DataType::UInt32 => PropType::U32,
143            DataType::UInt64 => PropType::U64,
144            DataType::Float32 => PropType::F32,
145            DataType::Float64 => PropType::F64,
146            DataType::Boolean => PropType::Bool,
147
148            _ => PropType::Empty,
149        }
150    }
151}
152
153// step through these types trees and check they are structurally the same
154// if we encounter an empty we replace it with the other type
155// the result is the unified type or err if the types are not compatible
156pub fn unify_types(l: &PropType, r: &PropType, unified: &mut bool) -> Result<PropType, PropError> {
157    match (l, r) {
158        (PropType::Empty, r) => {
159            *unified = true;
160            Ok(r.clone())
161        }
162        (l, PropType::Empty) => {
163            *unified = true;
164            Ok(l.clone())
165        }
166        (PropType::Str, PropType::Str) => Ok(PropType::Str),
167        (PropType::U8, PropType::U8) => Ok(PropType::U8),
168        (PropType::U16, PropType::U16) => Ok(PropType::U16),
169        (PropType::I32, PropType::I32) => Ok(PropType::I32),
170        (PropType::I64, PropType::I64) => Ok(PropType::I64),
171        (PropType::U32, PropType::U32) => Ok(PropType::U32),
172        (PropType::U64, PropType::U64) => Ok(PropType::U64),
173        (PropType::F32, PropType::F32) => Ok(PropType::F32),
174        (PropType::F64, PropType::F64) => Ok(PropType::F64),
175        (PropType::Bool, PropType::Bool) => Ok(PropType::Bool),
176        (PropType::NDTime, PropType::NDTime) => Ok(PropType::NDTime),
177        (PropType::DTime, PropType::DTime) => Ok(PropType::DTime),
178        (PropType::List(l_type), PropType::List(r_type)) => {
179            unify_types(l_type, r_type, unified).map(|t| PropType::List(Box::new(t)))
180        }
181        (PropType::Array(l_type), PropType::Array(r_type)) => {
182            unify_types(l_type, r_type, unified).map(|t| PropType::Array(Box::new(t)))
183        }
184        (PropType::Map(l_map), PropType::Map(r_map)) => {
185            // maps need to be merged and only overlapping keys need to be unified
186
187            let mut merged = HashMap::new();
188            for (k, v) in l_map.iter() {
189                if let Some(r_v) = r_map.get(k) {
190                    let merged_prop = unify_types(v, r_v, unified)?;
191                    merged.insert(k.clone(), merged_prop);
192                } else {
193                    merged.insert(k.clone(), v.clone());
194                    *unified = true;
195                }
196            }
197            for (k, v) in r_map.iter() {
198                if !merged.contains_key(k) {
199                    merged.insert(k.clone(), v.clone());
200                    *unified = true;
201                }
202            }
203            Ok(PropType::Map(merged))
204        }
205        (PropType::Decimal { scale: l_scale }, PropType::Decimal { scale: r_scale })
206            if l_scale == r_scale =>
207        {
208            Ok(PropType::Decimal { scale: *l_scale })
209        }
210        (_, _) => Err(PropError::PropertyTypeError {
211            name: "unknown".to_string(),
212            expected: l.clone(),
213            actual: r.clone(),
214        }),
215    }
216}
217
218#[cfg(test)]
219mod test {
220    use super::*;
221
222    #[test]
223    fn test_unify_types_ne() {
224        let l = PropType::List(Box::new(PropType::U8));
225        let r = PropType::List(Box::new(PropType::U16));
226        assert!(unify_types(&l, &r, &mut false).is_err());
227
228        let l = PropType::map([("a".to_string(), PropType::U8)]);
229        let r = PropType::map([("a".to_string(), PropType::U16)]);
230        assert!(unify_types(&l, &r, &mut false).is_err());
231
232        let l = PropType::List(Box::new(PropType::U8));
233        let r = PropType::List(Box::new(PropType::U16));
234        assert!(unify_types(&l, &r, &mut false).is_err());
235    }
236
237    #[test]
238    fn test_unify_types_eq() {
239        let l = PropType::List(Box::new(PropType::U8));
240        let r = PropType::List(Box::new(PropType::U8));
241        assert_eq!(
242            unify_types(&l, &r, &mut false),
243            Ok(PropType::List(Box::new(PropType::U8)))
244        );
245
246        let l = PropType::map([("a".to_string(), PropType::U8)]);
247        let r = PropType::map([("a".to_string(), PropType::U8)]);
248        assert_eq!(
249            unify_types(&l, &r, &mut false),
250            Ok(PropType::map([("a".to_string(), PropType::U8)]))
251        );
252    }
253
254    #[test]
255    fn test_unify_maps() {
256        let l = PropType::map([("a".to_string(), PropType::U8)]);
257        let r = PropType::map([("a".to_string(), PropType::U16)]);
258        assert!(unify_types(&l, &r, &mut false).is_err());
259
260        let l = PropType::map([("a".to_string(), PropType::U8)]);
261        let r = PropType::map([("b".to_string(), PropType::U16)]);
262        let mut unify = false;
263        assert_eq!(
264            unify_types(&l, &r, &mut unify),
265            Ok(PropType::map([
266                ("a".to_string(), PropType::U8),
267                ("b".to_string(), PropType::U16)
268            ]))
269        );
270        assert!(unify);
271
272        let l = PropType::map([("a".to_string(), PropType::U8)]);
273        let r = PropType::map([
274            ("a".to_string(), PropType::U8),
275            ("b".to_string(), PropType::U16),
276        ]);
277        let mut unify = false;
278        assert_eq!(
279            unify_types(&l, &r, &mut unify),
280            Ok(PropType::map([
281                ("a".to_string(), PropType::U8),
282                ("b".to_string(), PropType::U16)
283            ]))
284        );
285        assert!(unify);
286
287        let l = PropType::map([
288            ("a".to_string(), PropType::U8),
289            ("b".to_string(), PropType::U16),
290        ]);
291        let r = PropType::map([("a".to_string(), PropType::U8)]);
292        let mut unify = false;
293        assert_eq!(
294            unify_types(&l, &r, &mut unify),
295            Ok(PropType::map([
296                ("a".to_string(), PropType::U8),
297                ("b".to_string(), PropType::U16)
298            ]))
299        );
300        assert!(unify);
301    }
302
303    #[test]
304    fn test_unify() {
305        let l = PropType::Empty;
306        let r = PropType::U8;
307        let mut unify = false;
308        assert_eq!(unify_types(&l, &r, &mut unify), Ok(PropType::U8));
309        assert!(unify);
310
311        let l = PropType::Str;
312        let r = PropType::Empty;
313        let mut unify = false;
314        assert_eq!(unify_types(&l, &r, &mut unify), Ok(PropType::Str));
315        assert!(unify);
316
317        let l = PropType::List(Box::new(PropType::List(Box::new(PropType::U8))));
318        let r = PropType::List(Box::new(PropType::Empty));
319        let mut unify = false;
320        assert_eq!(
321            unify_types(&l, &r, &mut unify),
322            Ok(PropType::List(Box::new(PropType::List(Box::new(
323                PropType::U8
324            )))))
325        );
326        assert!(unify);
327
328        let l = PropType::Array(Box::new(PropType::map([("a".to_string(), PropType::U8)])));
329        let r = PropType::Array(Box::new(PropType::map([
330            ("a".to_string(), PropType::Empty),
331            ("b".to_string(), PropType::Str),
332        ])));
333        let mut unify = false;
334        assert_eq!(
335            unify_types(&l, &r, &mut unify),
336            Ok(PropType::Array(Box::new(PropType::map([
337                ("a".to_string(), PropType::U8),
338                ("b".to_string(), PropType::Str)
339            ]))))
340        );
341        assert!(unify);
342    }
343}