From d72f9c18556e4c821e5f0b0fa2799bdaa4e8363b Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Thu, 21 May 2026 11:13:45 -0400 Subject: [PATCH 1/9] Add row_size to WriteLockedPropMapper --- .../src/core/entities/properties/meta.rs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/raphtory-api/src/core/entities/properties/meta.rs b/raphtory-api/src/core/entities/properties/meta.rs index 6113f87596..224827ba12 100644 --- a/raphtory-api/src/core/entities/properties/meta.rs +++ b/raphtory-api/src/core/entities/properties/meta.rs @@ -300,8 +300,10 @@ impl PropMapper { let wrapped_id = self.id_mapper.get_or_create_id(prop); let id = wrapped_id.inner(); let dtype_read = self.dtypes.read_recursive(); + if let Some(old_type) = dtype_read.get(id) { let mut unified = false; + if unify_types(&dtype, old_type, &mut unified).is_ok() { if !unified { // means the types were equal, no change needed @@ -315,8 +317,13 @@ impl PropMapper { }); } } - drop(dtype_read); // drop the read lock and wait for write lock as type did not exist yet + + // Drop the read lock and grab the write lock in order to add the new + // prop type or unify the existing prop type. + drop(dtype_read); + let mut dtype_write = self.dtypes.write(); + match dtype_write.get(id).cloned() { Some(old_type) => { if let Ok(tpe) = unify_types(&dtype, &old_type, &mut false) { @@ -331,10 +338,12 @@ impl PropMapper { } } None => { - // vector not resized yet, resize it and set the dtype and return id + // vector not resized yet; resize it, set the new dtype and return the id. dtype_write.resize(id + 1, PropType::Empty); + self.row_size .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed); + dtype_write[id] = dtype; Ok(wrapped_id) } @@ -371,6 +380,7 @@ impl PropMapper { WriteLockedPropMapper { dict_mapper: self.id_mapper.write(), d_types: self.dtypes.write(), + row_size: &self.row_size, } } } @@ -383,6 +393,7 @@ pub struct LockedPropMapper<'a> { pub struct WriteLockedPropMapper<'a> { dict_mapper: WriteLockedDictMapper<'a>, d_types: RwLockWriteGuard<'a, Vec>, + row_size: &'a AtomicUsize, } impl<'a> WriteLockedPropMapper<'a> { @@ -421,17 +432,27 @@ impl<'a> WriteLockedPropMapper<'a> { pub fn set_dtype(&mut self, id: usize, dtype: PropType) { let dtypes = self.d_types.deref_mut(); + if dtypes.len() <= id { dtypes.resize(id + 1, PropType::Empty); } + + self.row_size + .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed); + dtypes[id] = dtype; } pub fn set_or_unify_dtype(&mut self, id: usize, dtype: PropType) -> Result<(), PropError> { let dtypes = self.d_types.deref_mut(); + match dtypes.get_mut(id) { None => { dtypes.resize(id + 1, PropType::Empty); + + self.row_size + .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed); + dtypes[id] = dtype; } Some(old_dtype) => { @@ -440,15 +461,21 @@ impl<'a> WriteLockedPropMapper<'a> { *old_dtype = unified_type; } } + Ok(()) } pub fn new_id_and_dtype(&mut self, key: impl Into, dtype: PropType) -> usize { let id = self.dict_mapper.get_or_create_id(&key.into()); let dtypes = self.d_types.deref_mut(); + if dtypes.len() <= id.inner() { dtypes.resize(id.inner() + 1, PropType::Empty); } + + self.row_size + .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed); + dtypes[id.inner()] = dtype; id.inner() } From fc34d8970d18bd07110f86c7a4fcb3e65474adfc Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Thu, 21 May 2026 12:08:50 -0400 Subject: [PATCH 2/9] Add some docs --- .../src/core/entities/properties/meta.rs | 98 +++++++++++-------- 1 file changed, 55 insertions(+), 43 deletions(-) diff --git a/raphtory-api/src/core/entities/properties/meta.rs b/raphtory-api/src/core/entities/properties/meta.rs index 224827ba12..7094196228 100644 --- a/raphtory-api/src/core/entities/properties/meta.rs +++ b/raphtory-api/src/core/entities/properties/meta.rs @@ -234,11 +234,17 @@ impl Meta { } } +/// Manages the mapping of property names to their IDs and types. #[derive(Default, Debug, Serialize, Deserialize)] pub struct PropMapper { + /// Maps property names to their IDs. id_mapper: DictMapper, - row_size: AtomicUsize, + + /// Property types indexed by property ID. dtypes: Arc>>, + + /// Estimated size in bytes of a single row of properties maintained by this mapper. + row_size: AtomicUsize, } impl Deref for PropMapper { @@ -385,34 +391,32 @@ impl PropMapper { } } -pub struct LockedPropMapper<'a> { - dict_mapper: LockedDictMapper<'a>, - d_types: RwLockReadGuard<'a, Vec>, -} - +/// Write-locked view of a [`PropMapper`]. pub struct WriteLockedPropMapper<'a> { + /// Maps property names to their IDs. dict_mapper: WriteLockedDictMapper<'a>, + + /// Property types indexed by property ID. d_types: RwLockWriteGuard<'a, Vec>, + + /// Estimated size in bytes of a single row of properties maintained by this mapper. row_size: &'a AtomicUsize, } impl<'a> WriteLockedPropMapper<'a> { - pub fn get_dtype(&'a self, prop_id: usize) -> Option<&'a PropType> { - self.d_types.get(prop_id) - } + pub fn new_id_and_dtype(&mut self, key: impl Into, dtype: PropType) -> usize { + let id = self.dict_mapper.get_or_create_id(&key.into()); + let dtypes = self.d_types.deref_mut(); - /// Fast check for property type without unifying the types - /// Returns: - /// - `Some(Either::Left(id))` if the property type can be unified - /// - `Some(Either::Right(id))` if the property type is already set and no unification is needed - /// - `None` if the property type is not set - /// - `Err(PropError::PropertyTypeError)` if the property type cannot be unified - pub fn fast_proptype_check( - &mut self, - prop: &str, - dtype: PropType, - ) -> Result>, PropError> { - fast_proptype_check(self.dict_mapper.map(), &self.d_types, prop, dtype) + if dtypes.len() <= id.inner() { + dtypes.resize(id.inner() + 1, PropType::Empty); + } + + self.row_size + .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed); + + dtypes[id.inner()] = dtype; + id.inner() } pub fn set_id_and_dtype(&mut self, key: impl Into, id: usize, dtype: PropType) { @@ -420,16 +424,6 @@ impl<'a> WriteLockedPropMapper<'a> { self.set_dtype(id, dtype); } - pub fn set_or_unify_id_and_dtype( - &mut self, - key: impl Into, - id: usize, - dtype: PropType, - ) -> Result<(), PropError> { - self.dict_mapper.set_id(key, id); - self.set_or_unify_dtype(id, dtype) - } - pub fn set_dtype(&mut self, id: usize, dtype: PropType) { let dtypes = self.d_types.deref_mut(); @@ -443,6 +437,16 @@ impl<'a> WriteLockedPropMapper<'a> { dtypes[id] = dtype; } + pub fn set_or_unify_id_and_dtype( + &mut self, + key: impl Into, + id: usize, + dtype: PropType, + ) -> Result<(), PropError> { + self.dict_mapper.set_id(key, id); + self.set_or_unify_dtype(id, dtype) + } + pub fn set_or_unify_dtype(&mut self, id: usize, dtype: PropType) -> Result<(), PropError> { let dtypes = self.d_types.deref_mut(); @@ -465,22 +469,30 @@ impl<'a> WriteLockedPropMapper<'a> { Ok(()) } - pub fn new_id_and_dtype(&mut self, key: impl Into, dtype: PropType) -> usize { - let id = self.dict_mapper.get_or_create_id(&key.into()); - let dtypes = self.d_types.deref_mut(); - - if dtypes.len() <= id.inner() { - dtypes.resize(id.inner() + 1, PropType::Empty); - } - - self.row_size - .fetch_add(dtype.est_size(), atomic::Ordering::Relaxed); + pub fn get_dtype(&'a self, prop_id: usize) -> Option<&'a PropType> { + self.d_types.get(prop_id) + } - dtypes[id.inner()] = dtype; - id.inner() + /// Fast check for property type without unifying the types + /// Returns: + /// - `Some(Either::Left(id))` if the property type can be unified + /// - `Some(Either::Right(id))` if the property type is already set and no unification is needed + /// - `None` if the property type is not set + /// - `Err(PropError::PropertyTypeError)` if the property type cannot be unified + pub fn fast_proptype_check( + &mut self, + prop: &str, + dtype: PropType, + ) -> Result>, PropError> { + fast_proptype_check(self.dict_mapper.map(), &self.d_types, prop, dtype) } } +pub struct LockedPropMapper<'a> { + dict_mapper: LockedDictMapper<'a>, + d_types: RwLockReadGuard<'a, Vec>, +} + impl<'a> LockedPropMapper<'a> { pub fn get_id(&self, prop: &str) -> Option { self.dict_mapper.get_id(prop) From 769c7f0261308f914154c531d9099aaa529aae4e Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Sun, 24 May 2026 13:07:53 -0400 Subject: [PATCH 3/9] Add some docs --- raphtory/tests/test_filters.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raphtory/tests/test_filters.rs b/raphtory/tests/test_filters.rs index 37038c2f0d..b427e99eb9 100644 --- a/raphtory/tests/test_filters.rs +++ b/raphtory/tests/test_filters.rs @@ -5111,6 +5111,7 @@ mod test_node_property_filter_agg { Prop::List(v.into()) } + /// Writes a set of node temporal properties and node metadata to the given graph. pub fn init_nodes_graph< G: StaticGraphViewOps + AdditionOps @@ -5120,6 +5121,7 @@ mod test_node_property_filter_agg { >( graph: G, ) -> G { + // Each tuple represents (timestamp, node_name, properties). let nodes: [(i64, &str, Vec<(&str, Prop)>); 12] = [ ( 1, @@ -5320,6 +5322,7 @@ mod test_node_property_filter_agg { graph.add_node(t, id, props, None, None).unwrap(); } + // Each tuple represents (node_name, properties). let metadata: [(&str, Vec<(&str, Prop)>); 8] = [ ( "n1", From 57f89822babe19eb0465b61601079f6cd3676906 Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Mon, 25 May 2026 13:39:31 -0400 Subject: [PATCH 4/9] Setup print debugging --- raphtory/src/db/graph/assertions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raphtory/src/db/graph/assertions.rs b/raphtory/src/db/graph/assertions.rs index 30b31dc1f9..a6b329163d 100644 --- a/raphtory/src/db/graph/assertions.rs +++ b/raphtory/src/db/graph/assertions.rs @@ -2,7 +2,7 @@ use crate::{ db::api::view::{filter_ops::Filter, StaticGraphViewOps}, prelude::{EdgeViewOps, Graph, GraphViewOps, NodeViewOps}, }; -use std::ops::Range; +use std::{ops::Range, thread, time::Duration}; #[cfg(feature = "search")] pub use crate::db::api::view::SearchableGraphOps; @@ -396,6 +396,8 @@ fn assert_results( let graph = init_graph(Graph::new()); + thread::sleep(Duration::from_secs(3)); + let expected = sorted(expected.iter()); for v in variants { From 87d32bdf7fbfbdb67fe1fad8f0c3ac118f463eef Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Mon, 25 May 2026 21:48:55 -0400 Subject: [PATCH 5/9] Some cleanup --- db4-storage/src/pages/node_page/writer.rs | 3 +++ raphtory/src/db/graph/assertions.rs | 23 ++++++++++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/db4-storage/src/pages/node_page/writer.rs b/db4-storage/src/pages/node_page/writer.rs index 5c462bc638..4b07c8b634 100644 --- a/db4-storage/src/pages/node_page/writer.rs +++ b/db4-storage/src/pages/node_page/writer.rs @@ -31,6 +31,7 @@ pub struct NodeWriter<'a, MP: DerefMut + 'a, NS: NodeSe impl<'a, MP: DerefMut + 'a, NS: NodeSegmentOps> NodeWriter<'a, MP, NS> { pub fn new(page: &'a NS, global_num_nodes: &'a GraphStats, writer: MP) -> Self { let old_est_size = writer.est_size(); + Self { page, mut_segment: writer, @@ -38,6 +39,7 @@ impl<'a, MP: DerefMut + 'a, NS: NodeSegmentOps> NodeWri old_est_size, } } + #[inline(always)] pub fn resolve_pos(&self, node_id: VID) -> Option { let (page, pos) = resolve_pos(node_id, self.mut_segment.max_page_len()); @@ -270,6 +272,7 @@ impl<'a, MP: DerefMut + 'a, NS: NodeSegmentOps> Drop fn drop(&mut self) { self.mut_segment .increment_global_est_size(self.mut_segment.est_size() - self.old_est_size); + self.page .notify_write(self.mut_segment.deref_mut()) .expect("Failed to persist node page"); diff --git a/raphtory/src/db/graph/assertions.rs b/raphtory/src/db/graph/assertions.rs index a6b329163d..0458d2242e 100644 --- a/raphtory/src/db/graph/assertions.rs +++ b/raphtory/src/db/graph/assertions.rs @@ -384,20 +384,7 @@ fn assert_results( variants: Vec, apply: impl ApplyFilter, ) { - fn sorted(iter: I) -> Vec - where - I: IntoIterator, - S: AsRef, - { - let mut v: Vec = iter.into_iter().map(|s| s.as_ref().to_string()).collect(); - v.sort(); - v - } - let graph = init_graph(Graph::new()); - - thread::sleep(Duration::from_secs(3)); - let expected = sorted(expected.iter()); for v in variants { @@ -419,6 +406,16 @@ fn assert_results( } } +fn sorted(iter: I) -> Vec +where + I: IntoIterator, + S: AsRef, +{ + let mut v: Vec = iter.into_iter().map(|s| s.as_ref().to_string()).collect(); + v.sort(); + v +} + pub fn filter_nodes(graph: &Graph, filter: impl CreateFilter) -> Vec { let mut results = graph .filter(filter) From 9a9888cd4782e0ea8d01fc79b86bf96cd6a0be97 Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Mon, 25 May 2026 22:11:27 -0400 Subject: [PATCH 6/9] Add test for estimated size on unified types --- .../src/core/entities/properties/meta.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/raphtory-api/src/core/entities/properties/meta.rs b/raphtory-api/src/core/entities/properties/meta.rs index 7094196228..4bab73d994 100644 --- a/raphtory-api/src/core/entities/properties/meta.rs +++ b/raphtory-api/src/core/entities/properties/meta.rs @@ -636,4 +636,36 @@ mod tests { assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); assert_eq!(prop_mapper.get_dtype(1), Some(PropType::U16)); } + + #[test] + fn test_unify_types_increases_row_size() { + let map_1 = PropType::map([("name", PropType::Str)]); + let map_2 = PropType::map([("location", PropType::Str)]); + + let mut unified = false; + let expected_type = unify_types(&map_1, &map_2, &mut unified).unwrap(); + let expected_delta = expected_type.est_size() - map_1.est_size(); + + assert!(unified); + assert!(expected_delta > 0, "should grow est_size on unify"); + + let prop_mapper = PropMapper::default(); + prop_mapper + .get_or_create_and_validate("attrs", map_1.clone()) + .unwrap(); + + let before = prop_mapper.row_size(); + + assert_eq!(before, map_1.est_size()); + + prop_mapper + .get_or_create_and_validate("attrs", map_2.clone()) + .unwrap(); + + let after = prop_mapper.row_size(); + + assert_eq!(after, before + expected_delta); + assert_eq!(after, expected_type.est_size()); + assert_eq!(prop_mapper.get_dtype(0), Some(expected_type)); + } } From 5287909ed42edc77246f33276521ab437371e9a8 Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Mon, 25 May 2026 22:28:41 -0400 Subject: [PATCH 7/9] Update row_size after unifying types --- .../src/core/entities/properties/meta.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/raphtory-api/src/core/entities/properties/meta.rs b/raphtory-api/src/core/entities/properties/meta.rs index 4bab73d994..5e94d1b8a3 100644 --- a/raphtory-api/src/core/entities/properties/meta.rs +++ b/raphtory-api/src/core/entities/properties/meta.rs @@ -332,7 +332,16 @@ impl PropMapper { match dtype_write.get(id).cloned() { Some(old_type) => { - if let Ok(tpe) = unify_types(&dtype, &old_type, &mut false) { + let mut unified = false; + + if let Ok(tpe) = unify_types(&dtype, &old_type, &mut unified) { + if unified { + // The row size needs to account for the difference in sizes + // between the newly unified type and the old type. + let delta = tpe.est_size() - old_type.est_size(); + self.row_size.fetch_add(delta, atomic::Ordering::Relaxed); + } + dtype_write[id] = tpe; Ok(wrapped_id) } else { @@ -462,6 +471,14 @@ impl<'a> WriteLockedPropMapper<'a> { Some(old_dtype) => { let mut unified = false; let unified_type = unify_types(&old_dtype, &dtype, &mut unified)?; + + if unified { + // The row size needs to account for the difference in sizes + // between the newly unified type and the old type. + let delta = unified_type.est_size() - old_dtype.est_size(); + self.row_size.fetch_add(delta, atomic::Ordering::Relaxed); + } + *old_dtype = unified_type; } } From fcaf7c99db0b55cfde899f982d35ae75e61e560a Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Mon, 25 May 2026 22:45:43 -0400 Subject: [PATCH 8/9] Add tests for WriteLockedPropMapper --- .../src/core/entities/properties/meta.rs | 171 +++++++++++++++++- 1 file changed, 161 insertions(+), 10 deletions(-) diff --git a/raphtory-api/src/core/entities/properties/meta.rs b/raphtory-api/src/core/entities/properties/meta.rs index 5e94d1b8a3..28f66e1c7a 100644 --- a/raphtory-api/src/core/entities/properties/meta.rs +++ b/raphtory-api/src/core/entities/properties/meta.rs @@ -573,44 +573,51 @@ fn fast_proptype_check( } #[cfg(test)] -mod tests { +mod prop_mapper_tests { use super::*; #[test] - fn test_get_or_create_and_validate_new_property() { + fn get_or_create_and_validate_new_property() { let prop_mapper = PropMapper::default(); let result = prop_mapper.get_or_create_and_validate("new_prop", PropType::U8); + assert!(result.is_ok()); assert_eq!(result.unwrap().inner(), 0); assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); } #[test] - fn test_get_or_create_and_validate_existing_property_same_type() { + fn get_or_create_and_validate_existing_property_same_type() { let prop_mapper = PropMapper::default(); + prop_mapper .get_or_create_and_validate("existing_prop", PropType::U8) .unwrap(); + let result = prop_mapper.get_or_create_and_validate("existing_prop", PropType::U8); + assert!(result.is_ok()); assert_eq!(result.unwrap().inner(), 0); assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); } #[test] - fn test_get_or_create_and_validate_existing_property_different_type() { + fn get_or_create_and_validate_existing_property_different_type() { let prop_mapper = PropMapper::default(); + prop_mapper .get_or_create_and_validate("existing_prop", PropType::U8) .unwrap(); + let result = prop_mapper.get_or_create_and_validate("existing_prop", PropType::U16); + assert!(result.is_err()); + if let Err(PropError { name, expected, actual, - }) = result - { + }) = result { assert_eq!(name, "existing_prop"); assert_eq!(expected, PropType::U8); assert_eq!(actual, PropType::U16); @@ -620,32 +627,39 @@ mod tests { } #[test] - fn test_get_or_create_and_validate_unify_types() { + fn get_or_create_and_validate_unify_types() { let prop_mapper = PropMapper::default(); + prop_mapper .get_or_create_and_validate("prop", PropType::Empty) .unwrap(); + let result = prop_mapper.get_or_create_and_validate("prop", PropType::U8); + assert!(result.is_ok()); assert_eq!(result.unwrap().inner(), 0); assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); } #[test] - fn test_get_or_create_and_validate_resize_vector() { + fn get_or_create_and_validate_resize_vector() { let prop_mapper = PropMapper::default(); + prop_mapper.set_id_and_dtype("existing_prop", 5, PropType::U8); + let result = prop_mapper.get_or_create_and_validate("new_prop", PropType::U16); + assert!(result.is_ok()); assert_eq!(result.unwrap().inner(), 6); assert_eq!(prop_mapper.get_dtype(6), Some(PropType::U16)); } #[test] - fn test_get_or_create_and_validate_two_independent_properties() { + fn get_or_create_and_validate_two_independent_properties() { let prop_mapper = PropMapper::default(); let result1 = prop_mapper.get_or_create_and_validate("prop1", PropType::U8); let result2 = prop_mapper.get_or_create_and_validate("prop2", PropType::U16); + assert!(result1.is_ok()); assert!(result2.is_ok()); assert_eq!(result1.unwrap().inner(), 0); @@ -655,7 +669,7 @@ mod tests { } #[test] - fn test_unify_types_increases_row_size() { + fn unify_types_increases_row_size() { let map_1 = PropType::map([("name", PropType::Str)]); let map_2 = PropType::map([("location", PropType::Str)]); @@ -686,3 +700,140 @@ mod tests { assert_eq!(prop_mapper.get_dtype(0), Some(expected_type)); } } + +#[cfg(test)] +mod write_locked_prop_mapper_tests { + use super::*; + + #[test] + fn new_id_and_dtype() { + let prop_mapper = PropMapper::default(); + + let id = { + let mut locked = prop_mapper.write_locked(); + locked.new_id_and_dtype("new_prop", PropType::U8) + }; + + assert_eq!(id, 0); + assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); + } + + #[test] + fn set_or_unify_existing_property_same_type() { + let prop_mapper = PropMapper::default(); + + let id = { + let mut locked = prop_mapper.write_locked(); + let id = locked.new_id_and_dtype("existing_prop", PropType::U8); + locked.set_or_unify_dtype(id, PropType::U8).unwrap(); + id + }; + + assert_eq!(id, 0); + assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); + } + + #[test] + fn set_or_unify_existing_property_different_type() { + let prop_mapper = PropMapper::default(); + + let result = { + let mut locked = prop_mapper.write_locked(); + let id = locked.new_id_and_dtype("existing_prop", PropType::U8); + + locked.set_or_unify_dtype(id, PropType::U16) + }; + + assert!(result.is_err()); + + if let Err(PropError { + expected, + actual, + .. + }) = result { + assert_eq!(expected, PropType::U8); + assert_eq!(actual, PropType::U16); + } else { + panic!("Expected PropError"); + } + } + + #[test] + fn set_or_unify_types() { + let prop_mapper = PropMapper::default(); + + let id = { + let mut locked = prop_mapper.write_locked(); + let id = locked.new_id_and_dtype("prop", PropType::Empty); + locked.set_or_unify_dtype(id, PropType::U8).unwrap(); + id + }; + + assert_eq!(id, 0); + assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); + } + + #[test] + fn new_id_and_dtype_resize_vector() { + let prop_mapper = PropMapper::default(); + + let id = { + let mut locked = prop_mapper.write_locked(); + locked.set_id_and_dtype("existing_prop", 5, PropType::U8); + locked.new_id_and_dtype("new_prop", PropType::U16) + }; + + assert_eq!(id, 6); + assert_eq!(prop_mapper.get_dtype(6), Some(PropType::U16)); + } + + #[test] + fn new_id_and_dtype_two_independent_properties() { + let prop_mapper = PropMapper::default(); + + let (id1, id2) = { + let mut locked = prop_mapper.write_locked(); + let id1 = locked.new_id_and_dtype("prop1", PropType::U8); + let id2 = locked.new_id_and_dtype("prop2", PropType::U16); + + (id1, id2) + }; + + assert_eq!(id1, 0); + assert_eq!(id2, 1); + assert_eq!(prop_mapper.get_dtype(0), Some(PropType::U8)); + assert_eq!(prop_mapper.get_dtype(1), Some(PropType::U16)); + } + + #[test] + fn unify_types_increases_row_size() { + let map_1 = PropType::map([("name", PropType::Str)]); + let map_2 = PropType::map([("location", PropType::Str)]); + + let mut unified = false; + let expected_type = unify_types(&map_1, &map_2, &mut unified).unwrap(); + let expected_delta = expected_type.est_size() - map_1.est_size(); + + assert!(unified); + assert!(expected_delta > 0, "should grow est_size on unify"); + + let prop_mapper = PropMapper::default(); + let id = { + let mut locked = prop_mapper.write_locked(); + locked.new_id_and_dtype("attrs", map_1.clone()) + }; + + let before = prop_mapper.row_size(); + assert_eq!(before, map_1.est_size()); + + { + let mut locked = prop_mapper.write_locked(); + locked.set_or_unify_dtype(id, map_2.clone()).unwrap(); + } + + let after = prop_mapper.row_size(); + assert_eq!(after, before + expected_delta); + assert_eq!(after, expected_type.est_size()); + assert_eq!(prop_mapper.get_dtype(0), Some(expected_type)); + } +} From d929add9f6eb7c74d33e4af6c07e5dd48d1c3b99 Mon Sep 17 00:00:00 2001 From: Fadhil Abubaker Date: Mon, 25 May 2026 22:53:01 -0400 Subject: [PATCH 9/9] Run fmt --- raphtory-api/src/core/entities/properties/meta.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raphtory-api/src/core/entities/properties/meta.rs b/raphtory-api/src/core/entities/properties/meta.rs index 28f66e1c7a..aaa932c236 100644 --- a/raphtory-api/src/core/entities/properties/meta.rs +++ b/raphtory-api/src/core/entities/properties/meta.rs @@ -617,7 +617,8 @@ mod prop_mapper_tests { name, expected, actual, - }) = result { + }) = result + { assert_eq!(name, "existing_prop"); assert_eq!(expected, PropType::U8); assert_eq!(actual, PropType::U16); @@ -747,10 +748,9 @@ mod write_locked_prop_mapper_tests { assert!(result.is_err()); if let Err(PropError { - expected, - actual, - .. - }) = result { + expected, actual, .. + }) = result + { assert_eq!(expected, PropType::U8); assert_eq!(actual, PropType::U16); } else {