Skip to main content

raphtory_api/core/entities/properties/
meta.rs

1use crate::core::{
2    entities::{
3        properties::prop::{check_for_unification, unify_types, PropError, PropType},
4        LayerId, LayerIds,
5    },
6    storage::{
7        arc_str::ArcStr,
8        dict_mapper::{DictMapper, LockedDictMapper, MaybeNew, PublicKeys, WriteLockedDictMapper},
9    },
10};
11use itertools::Either;
12use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
13use rustc_hash::FxHashMap;
14use serde::{Deserialize, Serialize};
15use std::{
16    fmt::{Debug, Formatter},
17    ops::{Deref, DerefMut},
18    sync::{
19        atomic::{self, AtomicUsize},
20        Arc,
21    },
22};
23
24// Internal const props for node id and type
25pub const NODE_ID_PROP_KEY: &str = "_raphtory_node_id";
26pub const NODE_ID_IDX: usize = 0;
27
28pub const NODE_TYPE_PROP_KEY: &str = "_raphtory_node_type";
29pub const NODE_TYPE_IDX: usize = 1;
30
31pub const STATIC_GRAPH_LAYER_NAME: &str = "_static_graph";
32pub const STATIC_GRAPH_LAYER_ID: LayerId = LayerId(0);
33
34pub const STATIC_GRAPH_LAYER: LayerIds = LayerIds::One(STATIC_GRAPH_LAYER_ID);
35
36/// The type ID for nodes that don't have a specified type.
37pub const DEFAULT_NODE_TYPE_ID: usize = 0;
38
39#[derive(Serialize, Deserialize, Debug, Default)]
40pub struct Meta {
41    temporal_prop_mapper: PropMapper,
42    metadata_mapper: PropMapper,
43    layer_mapper: DictMapper,
44    node_type_mapper: DictMapper,
45}
46
47impl Meta {
48    pub fn all_layer_iter(&self) -> impl Iterator<Item = (LayerId, ArcStr)> + use<'_> {
49        self.layer_mapper
50            .all_ids()
51            .map(LayerId)
52            .zip(self.layer_mapper.all_keys())
53    }
54
55    pub fn set_metadata_mapper(&mut self, meta: PropMapper) {
56        self.metadata_mapper = meta;
57    }
58
59    pub fn set_temporal_prop_mapper(&mut self, meta: PropMapper) {
60        self.temporal_prop_mapper = meta;
61    }
62
63    pub fn set_layer_mapper(&mut self, meta: DictMapper) {
64        self.layer_mapper = meta;
65    }
66    pub fn metadata_mapper(&self) -> &PropMapper {
67        &self.metadata_mapper
68    }
69
70    pub fn temporal_prop_mapper(&self) -> &PropMapper {
71        &self.temporal_prop_mapper
72    }
73
74    pub fn layer_meta(&self) -> &DictMapper {
75        &self.layer_mapper
76    }
77
78    pub fn node_type_meta(&self) -> &DictMapper {
79        &self.node_type_mapper
80    }
81
82    #[inline]
83    pub fn temporal_est_row_size(&self) -> usize {
84        self.temporal_prop_mapper.row_size()
85    }
86
87    #[inline]
88    pub fn const_est_row_size(&self) -> usize {
89        self.metadata_mapper.row_size()
90    }
91
92    pub fn new_for_nodes() -> Self {
93        let meta_layer = DictMapper::new_layer_mapper();
94        let meta_node_type = DictMapper::default();
95        meta_node_type.get_or_create_id("_default");
96
97        Self {
98            temporal_prop_mapper: PropMapper::default(),
99            metadata_mapper: PropMapper::new_with_private_fields(
100                [NODE_ID_PROP_KEY, NODE_TYPE_PROP_KEY],
101                [PropType::Empty, PropType::U64],
102            ),
103            layer_mapper: meta_layer,
104            node_type_mapper: meta_node_type, // type 0 is the default type for a node
105        }
106    }
107
108    pub fn new_for_edges() -> Self {
109        let meta_layer = DictMapper::new_layer_mapper();
110        let meta_node_type = DictMapper::default();
111        meta_node_type.get_or_create_id("_default");
112
113        Self {
114            temporal_prop_mapper: PropMapper::default(),
115            metadata_mapper: PropMapper::default(),
116            layer_mapper: meta_layer,
117            node_type_mapper: meta_node_type, // type 0 is the default type for a node
118        }
119    }
120
121    pub fn new_for_graph_props() -> Self {
122        let meta_layer = DictMapper::new_layer_mapper();
123        let meta_node_type = DictMapper::default();
124
125        // For now, only temporal and metadata mappers are used for graph metadata.
126        Self {
127            temporal_prop_mapper: PropMapper::default(),
128            metadata_mapper: PropMapper::default(),
129            layer_mapper: meta_layer,
130            node_type_mapper: meta_node_type,
131        }
132    }
133
134    #[inline]
135    pub fn resolve_prop_id(
136        &self,
137        prop: &str,
138        dtype: PropType,
139        is_static: bool,
140    ) -> Result<MaybeNew<usize>, PropError> {
141        if is_static {
142            self.metadata_mapper.get_or_create_and_validate(prop, dtype)
143        } else {
144            self.temporal_prop_mapper
145                .get_or_create_and_validate(prop, dtype)
146        }
147    }
148
149    pub fn get_prop_id(&self, name: &str, is_static: bool) -> Option<usize> {
150        if is_static {
151            self.metadata_mapper.get_id(name)
152        } else {
153            self.temporal_prop_mapper.get_id(name)
154        }
155    }
156
157    pub fn get_prop_id_and_type(&self, name: &str, is_static: bool) -> Option<(usize, PropType)> {
158        if is_static {
159            self.metadata_mapper.get_id_and_dtype(name)
160        } else {
161            self.temporal_prop_mapper.get_id_and_dtype(name)
162        }
163    }
164
165    #[inline]
166    pub fn get_or_create_layer_id(&self, name: Option<&str>) -> MaybeNew<LayerId> {
167        self.layer_mapper
168            .get_or_create_id(name.unwrap_or("_default"))
169            .map(|l| LayerId(l))
170    }
171
172    #[inline]
173    pub fn get_default_node_type_id(&self) -> usize {
174        DEFAULT_NODE_TYPE_ID
175    }
176
177    #[inline]
178    pub fn get_or_create_node_type_id(&self, node_type: &str) -> MaybeNew<usize> {
179        self.node_type_mapper.get_or_create_id(node_type)
180    }
181
182    #[inline]
183    pub fn get_layer_id(&self, name: &str) -> Option<LayerId> {
184        self.layer_mapper.get_id(name).map(|l| LayerId(l))
185    }
186
187    #[inline]
188    pub fn get_default_layer_id(&self) -> Option<LayerId> {
189        self.layer_mapper.get_id("_default").map(|id| LayerId(id))
190    }
191
192    #[inline]
193    pub fn get_node_type_id(&self, node_type: &str) -> Option<usize> {
194        self.node_type_mapper.get_id(node_type)
195    }
196
197    pub fn get_layer_name_by_id(&self, id: LayerId) -> ArcStr {
198        self.layer_mapper.get_name(id.0)
199    }
200
201    pub fn get_node_type_name_by_id(&self, id: usize) -> Option<ArcStr> {
202        if id == DEFAULT_NODE_TYPE_ID {
203            None
204        } else {
205            Some(self.node_type_mapper.get_name(id))
206        }
207    }
208
209    pub fn get_all_node_types(&self) -> Vec<ArcStr> {
210        self.node_type_mapper
211            .keys()
212            .iter()
213            .filter_map(|key| {
214                if key != "_default" {
215                    Some(key.clone())
216                } else {
217                    None
218                }
219            })
220            .collect()
221    }
222
223    pub fn get_all_property_names(&self, is_static: bool) -> PublicKeys<ArcStr> {
224        if is_static {
225            self.metadata_mapper.keys()
226        } else {
227            self.temporal_prop_mapper.keys()
228        }
229    }
230
231    pub fn get_prop_name(&self, prop_id: usize, is_static: bool) -> ArcStr {
232        if is_static {
233            self.metadata_mapper.get_name(prop_id)
234        } else {
235            self.temporal_prop_mapper.get_name(prop_id)
236        }
237    }
238}
239
240/// Manages the mapping of property names to their IDs and types.
241#[derive(Default, Serialize, Deserialize)]
242pub struct PropMapper {
243    /// Maps property names to their IDs.
244    id_mapper: DictMapper,
245
246    /// Property types indexed by property ID.
247    dtypes: Arc<RwLock<Vec<PropType>>>,
248
249    /// Estimated size in bytes of a single row of properties maintained by this mapper.
250    row_size: AtomicUsize,
251}
252
253impl Debug for PropMapper {
254    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
255        f.write_str("{")?;
256        for (k, (id, dtype)) in self
257            .all_keys()
258            .iter()
259            .zip(self.all_ids().zip(self.d_types().iter()))
260        {
261            write!(f, "{k}: ({id}, {dtype:?}), ")?;
262        }
263        f.write_str("}")
264    }
265}
266
267impl Deref for PropMapper {
268    type Target = DictMapper;
269
270    #[inline]
271    fn deref(&self) -> &Self::Target {
272        &self.id_mapper
273    }
274}
275
276impl PropMapper {
277    pub fn new_with_private_fields(
278        fields: impl IntoIterator<Item = impl Into<ArcStr>>,
279        dtypes: impl IntoIterator<Item = PropType>,
280    ) -> Self {
281        let dtypes = Vec::from_iter(dtypes);
282        let row_size = dtypes.iter().map(|dtype| dtype.est_size()).sum();
283
284        PropMapper {
285            id_mapper: DictMapper::new_with_private_fields(fields),
286            row_size: AtomicUsize::new(row_size),
287            dtypes: Arc::new(RwLock::new(dtypes)),
288        }
289    }
290
291    pub fn d_types(&self) -> impl Deref<Target = Vec<PropType>> + '_ {
292        self.dtypes.read_recursive()
293    }
294
295    pub fn deep_clone(&self) -> Self {
296        let dtypes = self.dtypes.read_recursive().clone();
297        Self {
298            id_mapper: self.id_mapper.deep_clone(),
299            row_size: AtomicUsize::new(self.row_size.load(std::sync::atomic::Ordering::Relaxed)),
300            dtypes: Arc::new(RwLock::new(dtypes)),
301        }
302    }
303
304    #[inline]
305    pub fn row_size(&self) -> usize {
306        self.row_size.load(atomic::Ordering::Relaxed)
307    }
308
309    pub fn get_id_and_dtype(&self, prop: &str) -> Option<(usize, PropType)> {
310        self.get_id(prop).map(|id| {
311            let existing_dtype = self
312                .get_dtype(id)
313                .expect("Existing id should always have a dtype");
314            (id, existing_dtype)
315        })
316    }
317
318    pub fn get_or_create_and_validate(
319        &self,
320        prop: &str,
321        dtype: PropType,
322    ) -> Result<MaybeNew<usize>, PropError> {
323        let wrapped_id = self.id_mapper.get_or_create_id(prop);
324        let id = wrapped_id.inner();
325        let dtype_read = self.dtypes.read_recursive();
326
327        if let Some(old_type) = dtype_read.get(id) {
328            let mut unified = false;
329
330            if unify_types(&dtype, old_type, &mut unified).is_ok() {
331                if !unified {
332                    // means the types were equal, no change needed
333                    return Ok(wrapped_id);
334                }
335            } else {
336                return Err(PropError {
337                    name: prop.to_owned(),
338                    expected: old_type.clone(),
339                    actual: dtype,
340                });
341            }
342        }
343
344        // Drop the read lock and grab the write lock in order to add the new
345        // prop type or unify the existing prop type.
346        drop(dtype_read);
347
348        let mut dtype_write = self.dtypes.write();
349
350        match dtype_write.get(id).cloned() {
351            Some(old_type) => {
352                let mut unified = false;
353
354                if let Ok(tpe) = unify_types(&dtype, &old_type, &mut unified) {
355                    if unified {
356                        // The row size needs to account for the difference in sizes
357                        // between the newly unified type and the old type.
358                        let delta = tpe.est_size() - old_type.est_size();
359                        self.row_size.fetch_add(delta, atomic::Ordering::Relaxed);
360                    }
361
362                    dtype_write[id] = tpe;
363                    Ok(wrapped_id)
364                } else {
365                    Err(PropError {
366                        name: prop.to_owned(),
367                        expected: old_type,
368                        actual: dtype,
369                    })
370                }
371            }
372            None => {
373                // vector not resized yet; resize it, set the new dtype and return the id.
374                dtype_write.resize(id + 1, PropType::Empty);
375
376                self.row_size
377                    .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed);
378
379                dtype_write[id] = dtype;
380                Ok(wrapped_id)
381            }
382        }
383    }
384
385    pub fn set_id_and_dtype(&self, key: impl Into<ArcStr>, id: usize, dtype: PropType) {
386        self.set_id(key, id);
387        self.set_dtype(id, dtype);
388    }
389
390    pub fn set_dtype(&self, id: usize, dtype: PropType) {
391        let mut dtypes = self.dtypes.write();
392        if dtypes.len() <= id {
393            dtypes.resize(id + 1, PropType::Empty);
394        }
395        self.row_size
396            .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed);
397        dtypes[id] = dtype;
398    }
399
400    pub fn get_dtype(&self, prop_id: usize) -> Option<PropType> {
401        self.dtypes.read_recursive().get(prop_id).cloned()
402    }
403
404    pub fn locked(&self) -> LockedPropMapper<'_> {
405        LockedPropMapper {
406            dict_mapper: self.id_mapper.read(),
407            d_types: self.dtypes.read_recursive(),
408        }
409    }
410
411    pub fn write_locked(&self) -> WriteLockedPropMapper<'_> {
412        WriteLockedPropMapper {
413            dict_mapper: self.id_mapper.write(),
414            d_types: self.dtypes.write(),
415            row_size: &self.row_size,
416        }
417    }
418}
419
420/// Write-locked view of a [`PropMapper`].
421pub struct WriteLockedPropMapper<'a> {
422    /// Maps property names to their IDs.
423    dict_mapper: WriteLockedDictMapper<'a>,
424
425    /// Property types indexed by property ID.
426    d_types: RwLockWriteGuard<'a, Vec<PropType>>,
427
428    /// Estimated size in bytes of a single row of properties maintained by this mapper.
429    row_size: &'a AtomicUsize,
430}
431
432impl<'a> WriteLockedPropMapper<'a> {
433    pub fn new_id_and_dtype(&mut self, key: impl Into<ArcStr>, dtype: PropType) -> usize {
434        let id = self.dict_mapper.get_or_create_id(&key.into());
435        let dtypes = self.d_types.deref_mut();
436
437        if dtypes.len() <= id.inner() {
438            dtypes.resize(id.inner() + 1, PropType::Empty);
439        }
440
441        self.row_size
442            .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed);
443
444        dtypes[id.inner()] = dtype;
445        id.inner()
446    }
447
448    pub fn set_id_and_dtype(&mut self, key: impl Into<ArcStr>, id: usize, dtype: PropType) {
449        self.dict_mapper.set_id(key, id);
450        self.set_dtype(id, dtype);
451    }
452
453    pub fn set_dtype(&mut self, id: usize, dtype: PropType) {
454        let dtypes = self.d_types.deref_mut();
455
456        if dtypes.len() <= id {
457            dtypes.resize(id + 1, PropType::Empty);
458        }
459
460        self.row_size
461            .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed);
462
463        dtypes[id] = dtype;
464    }
465
466    pub fn set_or_unify_id_and_dtype(
467        &mut self,
468        key: impl Into<ArcStr>,
469        id: usize,
470        dtype: PropType,
471    ) -> Result<(), PropError> {
472        self.dict_mapper.set_id(key, id);
473        self.set_or_unify_dtype(id, dtype)
474    }
475
476    pub fn set_or_unify_dtype(&mut self, id: usize, dtype: PropType) -> Result<(), PropError> {
477        let dtypes = self.d_types.deref_mut();
478
479        match dtypes.get_mut(id) {
480            None => {
481                dtypes.resize(id + 1, PropType::Empty);
482
483                self.row_size
484                    .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed);
485
486                dtypes[id] = dtype;
487            }
488            Some(old_dtype) => {
489                let mut unified = false;
490                let unified_type = unify_types(&old_dtype, &dtype, &mut unified)?;
491
492                if unified {
493                    // The row size needs to account for the difference in sizes
494                    // between the newly unified type and the old type.
495                    let delta = unified_type.est_size() - old_dtype.est_size();
496                    self.row_size.fetch_add(delta, atomic::Ordering::Relaxed);
497                }
498
499                *old_dtype = unified_type;
500            }
501        }
502
503        Ok(())
504    }
505
506    pub fn get_dtype(&'a self, prop_id: usize) -> Option<&'a PropType> {
507        self.d_types.get(prop_id)
508    }
509
510    /// Fast check for property type without unifying the types
511    /// Returns:
512    /// - `Some(Either::Left(id))` if the property type can be unified
513    /// - `Some(Either::Right(id))` if the property type is already set and no unification is needed
514    /// - `None` if the property type is not set
515    /// - `Err(PropError::PropertyTypeError)` if the property type cannot be unified
516    pub fn fast_proptype_check(
517        &mut self,
518        prop: &str,
519        dtype: PropType,
520    ) -> Result<Option<Either<usize, usize>>, PropError> {
521        fast_proptype_check(self.dict_mapper.map(), &self.d_types, prop, dtype)
522    }
523}
524
525pub struct LockedPropMapper<'a> {
526    dict_mapper: LockedDictMapper<'a>,
527    d_types: RwLockReadGuard<'a, Vec<PropType>>,
528}
529
530impl<'a> LockedPropMapper<'a> {
531    pub fn get_id(&self, prop: &str) -> Option<usize> {
532        self.dict_mapper.get_id(prop)
533    }
534
535    pub fn get_dtype(&'a self, prop_id: usize) -> Option<&'a PropType> {
536        self.d_types.get(prop_id)
537    }
538
539    /// Fast check for property type without unifying the types
540    /// Returns:
541    /// - `Some(Either::Left(id))` if the property type can be unified
542    /// - `Some(Either::Right(id))` if the property type is already set and no unification is needed
543    /// - `None` if the property type is not set
544    /// - `Err(PropError::PropertyTypeError)` if the property type cannot be unified
545    pub fn fast_proptype_check(
546        &self,
547        prop: &str,
548        dtype: PropType,
549    ) -> Result<Option<Either<usize, usize>>, PropError> {
550        fast_proptype_check(self.dict_mapper.map(), &self.d_types, prop, dtype)
551    }
552
553    pub fn iter_ids_and_types(&self) -> impl Iterator<Item = (usize, &ArcStr, &PropType)> {
554        self.dict_mapper
555            .iter_ids()
556            .map(move |(id, name)| (id, name, &self.d_types[id]))
557    }
558}
559
560fn fast_proptype_check(
561    mapper: &FxHashMap<ArcStr, usize>,
562    d_types: &[PropType],
563    prop: &str,
564    dtype: PropType,
565) -> Result<Option<Either<usize, usize>>, PropError> {
566    match mapper.get(prop) {
567        Some(&id) => {
568            let existing_dtype = d_types
569                .get(id)
570                .expect("Existing id should always have a dtype");
571
572            let fast_check = check_for_unification(&dtype, existing_dtype);
573            if fast_check.is_none() {
574                // means nothing to do
575                return Ok(Some(Either::Right(id)));
576            }
577            let can_unify = fast_check.unwrap();
578            if can_unify {
579                Ok(Some(Either::Left(id)))
580            } else {
581                Err(PropError {
582                    name: prop.to_string(),
583                    expected: existing_dtype.clone(),
584                    actual: dtype,
585                })
586            }
587        }
588        None => Ok(None),
589    }
590}
591
592#[cfg(test)]
593mod prop_mapper_tests {
594    use super::*;
595
596    #[test]
597    fn get_or_create_and_validate_new_property() {
598        let prop_mapper = PropMapper::default();
599        let result = prop_mapper.get_or_create_and_validate("new_prop", PropType::U8);
600
601        assert!(result.is_ok());
602        assert_eq!(result.unwrap().inner(), 0);
603        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
604    }
605
606    #[test]
607    fn get_or_create_and_validate_existing_property_same_type() {
608        let prop_mapper = PropMapper::default();
609
610        prop_mapper
611            .get_or_create_and_validate("existing_prop", PropType::U8)
612            .unwrap();
613
614        let result = prop_mapper.get_or_create_and_validate("existing_prop", PropType::U8);
615
616        assert!(result.is_ok());
617        assert_eq!(result.unwrap().inner(), 0);
618        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
619    }
620
621    #[test]
622    fn get_or_create_and_validate_existing_property_different_type() {
623        let prop_mapper = PropMapper::default();
624
625        prop_mapper
626            .get_or_create_and_validate("existing_prop", PropType::U8)
627            .unwrap();
628
629        let result = prop_mapper.get_or_create_and_validate("existing_prop", PropType::U16);
630
631        assert!(result.is_err());
632
633        if let Err(PropError {
634            name,
635            expected,
636            actual,
637        }) = result
638        {
639            assert_eq!(name, "existing_prop");
640            assert_eq!(expected, PropType::U8);
641            assert_eq!(actual, PropType::U16);
642        } else {
643            panic!("Expected PropertyTypeError");
644        }
645    }
646
647    #[test]
648    fn get_or_create_and_validate_unify_types() {
649        let prop_mapper = PropMapper::default();
650
651        prop_mapper
652            .get_or_create_and_validate("prop", PropType::Empty)
653            .unwrap();
654
655        let result = prop_mapper.get_or_create_and_validate("prop", PropType::U8);
656
657        assert!(result.is_ok());
658        assert_eq!(result.unwrap().inner(), 0);
659        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
660    }
661
662    #[test]
663    fn get_or_create_and_validate_resize_vector() {
664        let prop_mapper = PropMapper::default();
665
666        prop_mapper.set_id_and_dtype("existing_prop", 5, PropType::U8);
667
668        let result = prop_mapper.get_or_create_and_validate("new_prop", PropType::U16);
669
670        assert!(result.is_ok());
671        assert_eq!(result.unwrap().inner(), 6);
672        assert_eq!(prop_mapper.get_dtype(6), Some(PropType::U16));
673    }
674
675    #[test]
676    fn get_or_create_and_validate_two_independent_properties() {
677        let prop_mapper = PropMapper::default();
678        let result1 = prop_mapper.get_or_create_and_validate("prop1", PropType::U8);
679        let result2 = prop_mapper.get_or_create_and_validate("prop2", PropType::U16);
680
681        assert!(result1.is_ok());
682        assert!(result2.is_ok());
683        assert_eq!(result1.unwrap().inner(), 0);
684        assert_eq!(result2.unwrap().inner(), 1);
685        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
686        assert_eq!(prop_mapper.get_dtype(1), Some(PropType::U16));
687    }
688
689    #[test]
690    fn unify_types_increases_row_size() {
691        let map_1 = PropType::map([("name", PropType::Str)]);
692        let map_2 = PropType::map([("location", PropType::Str)]);
693
694        let mut unified = false;
695        let expected_type = unify_types(&map_1, &map_2, &mut unified).unwrap();
696        let expected_delta = expected_type.est_size() - map_1.est_size();
697
698        assert!(unified);
699        assert!(expected_delta > 0, "should grow est_size on unify");
700
701        let prop_mapper = PropMapper::default();
702        prop_mapper
703            .get_or_create_and_validate("attrs", map_1.clone())
704            .unwrap();
705
706        let before = prop_mapper.row_size();
707
708        assert_eq!(before, map_1.est_size());
709
710        prop_mapper
711            .get_or_create_and_validate("attrs", map_2.clone())
712            .unwrap();
713
714        let after = prop_mapper.row_size();
715
716        assert_eq!(after, before + expected_delta);
717        assert_eq!(after, expected_type.est_size());
718        assert_eq!(prop_mapper.get_dtype(0), Some(expected_type));
719    }
720}
721
722#[cfg(test)]
723mod write_locked_prop_mapper_tests {
724    use super::*;
725
726    #[test]
727    fn new_id_and_dtype() {
728        let prop_mapper = PropMapper::default();
729
730        let id = {
731            let mut locked = prop_mapper.write_locked();
732            locked.new_id_and_dtype("new_prop", PropType::U8)
733        };
734
735        assert_eq!(id, 0);
736        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
737    }
738
739    #[test]
740    fn set_or_unify_existing_property_same_type() {
741        let prop_mapper = PropMapper::default();
742
743        let id = {
744            let mut locked = prop_mapper.write_locked();
745            let id = locked.new_id_and_dtype("existing_prop", PropType::U8);
746            locked.set_or_unify_dtype(id, PropType::U8).unwrap();
747            id
748        };
749
750        assert_eq!(id, 0);
751        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
752    }
753
754    #[test]
755    fn set_or_unify_existing_property_different_type() {
756        let prop_mapper = PropMapper::default();
757
758        let result = {
759            let mut locked = prop_mapper.write_locked();
760            let id = locked.new_id_and_dtype("existing_prop", PropType::U8);
761
762            locked.set_or_unify_dtype(id, PropType::U16)
763        };
764
765        assert!(result.is_err());
766
767        if let Err(PropError {
768            expected, actual, ..
769        }) = result
770        {
771            assert_eq!(expected, PropType::U8);
772            assert_eq!(actual, PropType::U16);
773        } else {
774            panic!("Expected PropError");
775        }
776    }
777
778    #[test]
779    fn set_or_unify_types() {
780        let prop_mapper = PropMapper::default();
781
782        let id = {
783            let mut locked = prop_mapper.write_locked();
784            let id = locked.new_id_and_dtype("prop", PropType::Empty);
785            locked.set_or_unify_dtype(id, PropType::U8).unwrap();
786            id
787        };
788
789        assert_eq!(id, 0);
790        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
791    }
792
793    #[test]
794    fn new_id_and_dtype_resize_vector() {
795        let prop_mapper = PropMapper::default();
796
797        let id = {
798            let mut locked = prop_mapper.write_locked();
799            locked.set_id_and_dtype("existing_prop", 5, PropType::U8);
800            locked.new_id_and_dtype("new_prop", PropType::U16)
801        };
802
803        assert_eq!(id, 6);
804        assert_eq!(prop_mapper.get_dtype(6), Some(PropType::U16));
805    }
806
807    #[test]
808    fn new_id_and_dtype_two_independent_properties() {
809        let prop_mapper = PropMapper::default();
810
811        let (id1, id2) = {
812            let mut locked = prop_mapper.write_locked();
813            let id1 = locked.new_id_and_dtype("prop1", PropType::U8);
814            let id2 = locked.new_id_and_dtype("prop2", PropType::U16);
815
816            (id1, id2)
817        };
818
819        assert_eq!(id1, 0);
820        assert_eq!(id2, 1);
821        assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8));
822        assert_eq!(prop_mapper.get_dtype(1), Some(PropType::U16));
823    }
824
825    #[test]
826    fn unify_types_increases_row_size() {
827        let map_1 = PropType::map([("name", PropType::Str)]);
828        let map_2 = PropType::map([("location", PropType::Str)]);
829
830        let mut unified = false;
831        let expected_type = unify_types(&map_1, &map_2, &mut unified).unwrap();
832        let expected_delta = expected_type.est_size() - map_1.est_size();
833
834        assert!(unified);
835        assert!(expected_delta > 0, "should grow est_size on unify");
836
837        let prop_mapper = PropMapper::default();
838        let id = {
839            let mut locked = prop_mapper.write_locked();
840            locked.new_id_and_dtype("attrs", map_1.clone())
841        };
842
843        let before = prop_mapper.row_size();
844        assert_eq!(before, map_1.est_size());
845
846        {
847            let mut locked = prop_mapper.write_locked();
848            locked.set_or_unify_dtype(id, map_2.clone()).unwrap();
849        }
850
851        let after = prop_mapper.row_size();
852        assert_eq!(after, before + expected_delta);
853        assert_eq!(after, expected_type.est_size());
854        assert_eq!(prop_mapper.get_dtype(0), Some(expected_type));
855    }
856}