From cda681511ea17e316b6a3cb8b6f763ac081f8158 Mon Sep 17 00:00:00 2001 From: "Kevin R. Thornton" Date: Tue, 8 Nov 2022 16:49:32 -0800 Subject: [PATCH] feat: add row_view() for tables --- src/edge_table.rs | 23 ++++++++++++++++++++ src/individual_table.rs | 22 +++++++++++++++++++ src/migration_table.rs | 25 ++++++++++++++++++++++ src/mutation_table.rs | 24 +++++++++++++++++++++ src/node_table.rs | 22 +++++++++++++++++++ src/population_table.rs | 24 +++++++++++++++++++++ src/provenance.rs | 47 +++++++++++++++++++++++++++++++++++++++++ src/site_table.rs | 21 ++++++++++++++++++ tests/test_tables.rs | 13 +++++++++++- 9 files changed, 220 insertions(+), 1 deletion(-) diff --git a/src/edge_table.rs b/src/edge_table.rs index 296b453dd..ca4a4e0ed 100644 --- a/src/edge_table.rs +++ b/src/edge_table.rs @@ -289,6 +289,29 @@ impl EdgeTable { pub fn row + Copy>(&self, r: E) -> Option { table_row_access!(r.into().0, self, make_edge_table_row) } + + /// Return a view of row `r` of the table. + /// + /// # Parameters + /// + /// * `r`: the row id. + /// + /// # Returns + /// + /// * `Some(row_view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, r: E) -> Option { + let view = EdgeTableRowView { + table: self, + id: r.into(), + left: self.left(r)?, + right: self.right(r)?, + parent: self.parent(r)?, + child: self.child(r)?, + metadata: self.raw_metadata(r.into()), + }; + Some(view) + } } build_owned_table_type!( diff --git a/src/individual_table.rs b/src/individual_table.rs index 8f52cb586..9b140023b 100644 --- a/src/individual_table.rs +++ b/src/individual_table.rs @@ -422,6 +422,28 @@ match tables.individuals().metadata::(0.into()) let ri = r.into().0; table_row_access!(ri, self, make_individual_table_row) } + + /// Return a view of `r` of the table. + /// + /// # Parameters + /// + /// * `r`: the row id. + /// + /// # Returns + /// + /// * `Some(row view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, r: I) -> Option { + let view = IndividualTableRowView { + table: self, + id: r.into(), + flags: self.flags(r)?, + location: self.location(r), + parents: self.parents(r), + metadata: self.raw_metadata(r.into()), + }; + Some(view) + } } build_owned_table_type!( diff --git a/src/migration_table.rs b/src/migration_table.rs index 7e3f4b339..5aad46991 100644 --- a/src/migration_table.rs +++ b/src/migration_table.rs @@ -340,6 +340,31 @@ impl MigrationTable { let ri = r.into().0; table_row_access!(ri, self, make_migration_table_row) } + + /// Return a view of `r` of the table. + /// + /// # Parameters + /// + /// * `r`: the row id. + /// + /// # Returns + /// + /// * `Some(row view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, r: M) -> Option { + let view = MigrationTableRowView { + table: self, + id: r.into(), + left: self.left(r)?, + right: self.right(r)?, + node: self.node(r)?, + source: self.source(r)?, + dest: self.dest(r)?, + time: self.time(r)?, + metadata: self.raw_metadata(r.into()), + }; + Some(view) + } } build_owned_table_type!( diff --git a/src/mutation_table.rs b/src/mutation_table.rs index e78582d32..896ae1910 100644 --- a/src/mutation_table.rs +++ b/src/mutation_table.rs @@ -320,6 +320,30 @@ impl MutationTable { let ri = r.into().0; table_row_access!(ri, self, make_mutation_table_row) } + + /// Return a view of row `r` of the table. + /// + /// # Parameters + /// + /// * `r`: the row id. + /// + /// # Returns + /// + /// * `Some(row view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, r: M) -> Option { + let view = MutationTableRowView { + table: self, + id: r.into(), + site: self.site(r)?, + node: self.node(r)?, + parent: self.parent(r)?, + time: self.time(r)?, + derived_state: self.derived_state(r), + metadata: self.raw_metadata(r.into()), + }; + Some(view) + } } build_owned_table_type!( diff --git a/src/node_table.rs b/src/node_table.rs index 023bce321..d07eff8c8 100644 --- a/src/node_table.rs +++ b/src/node_table.rs @@ -483,6 +483,28 @@ impl NodeTable { table_row_access!(ri, self, make_node_table_row) } + /// Return a view of row `r` of the table. + /// + /// # Parameters + /// + /// * `r`: the row id. + /// + /// # Returns + /// + /// * `Some(row view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, r: N) -> Option { + let view = NodeTableRowView { + table: self, + id: r.into(), + time: self.time(r)?, + flags: self.flags(r)?, + population: self.population(r)?, + individual: self.individual(r)?, + metadata: self.raw_metadata(r.into()), + }; + Some(view) + } /// Obtain a vector containing the indexes ("ids") /// of all nodes for which [`crate::TSK_NODE_IS_SAMPLE`] /// is `true`. diff --git a/src/population_table.rs b/src/population_table.rs index eb94200ff..b67084be6 100644 --- a/src/population_table.rs +++ b/src/population_table.rs @@ -191,6 +191,30 @@ impl PopulationTable { let ri = r.into().0; table_row_access!(ri, self, make_population_table_row) } + + /// Return a view of row `r` of the table. + /// + /// # Parameters + /// + /// * `r`: the row id. + /// + /// # Returns + /// + /// * `Some(row view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, r: P) -> Option { + match SizeType::try_from(r.into().0).ok() { + Some(row) if row < self.num_rows() => { + let view = PopulationTableRowView { + table: self, + id: r.into(), + metadata: self.raw_metadata(r.into()), + }; + Some(view) + } + _ => None, + } + } } build_owned_table_type!( diff --git a/src/provenance.rs b/src/provenance.rs index 0282b5d54..3d5c06417 100644 --- a/src/provenance.rs +++ b/src/provenance.rs @@ -122,6 +122,7 @@ impl<'a> streaming_iterator::StreamingIterator for ProvenanceTableRowView<'a> { row_lending_iterator_get!(); + // FIXME: bad duplication fn advance(&mut self) { self.id = (i32::from(self.id) + 1).into(); let record_slice = unsafe_tsk_ragged_char_column_access_to_slice_u8!( @@ -262,6 +263,52 @@ impl ProvenanceTable { make_provenance_row(self, row.into().0) } + /// Obtain a [`ProvenanceTableRowView`] for row `row`. + /// + /// # Returns + /// + /// * `Some(row view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, row: P) -> Option { + match u64::try_from(row.into().0).ok() { + // FIXME: bad duplication + Some(x) if x < self.num_rows() => { + let record_slice = unsafe_tsk_ragged_char_column_access_to_slice_u8!( + row.into().0, + 0, + self.num_rows(), + self.as_ref(), + record, + record_offset, + record_length + ); + let timestamp_slice = unsafe_tsk_ragged_char_column_access_to_slice_u8!( + row.into().0, + 0, + self.num_rows(), + self.as_ref(), + timestamp, + timestamp_offset, + timestamp_length + ); + let view = ProvenanceTableRowView { + table: self, + id: row.into(), + record: match record_slice { + Some(r) => std::str::from_utf8(r).unwrap(), + None => "", + }, + timestamp: match timestamp_slice { + Some(t) => std::str::from_utf8(t).unwrap(), + None => "", + }, + }; + Some(view) + } + _ => None, + } + } + /// Return an iterator over rows of the table. /// The value of the iterator is [`ProvenanceTableRow`]. pub fn iter(&self) -> impl Iterator + '_ { diff --git a/src/site_table.rs b/src/site_table.rs index f33c396b5..36f3d6384 100644 --- a/src/site_table.rs +++ b/src/site_table.rs @@ -242,6 +242,27 @@ impl SiteTable { let ri = r.into().0; table_row_access!(ri, self, make_site_table_row) } + + /// Return a view of row `r` of the table. + /// + /// # Parameters + /// + /// * `r`: the row id. + /// + /// # Returns + /// + /// * `Some(row view)` if `r` is valid + /// * `None` otherwise + pub fn row_view + Copy>(&self, r: S) -> Option { + let view = SiteTableRowView { + table: self, + id: r.into(), + position: self.position(r)?, + ancestral_state: self.ancestral_state(r), + metadata: self.raw_metadata(r.into()), + }; + Some(view) + } } build_owned_table_type!( diff --git a/tests/test_tables.rs b/tests/test_tables.rs index 715a615e1..1bfea8c6f 100644 --- a/tests/test_tables.rs +++ b/tests/test_tables.rs @@ -3,6 +3,7 @@ fn test_empty_table_collection() { macro_rules! validate_empty_tables { ($tables: ident, $table: ident, $table_iter: ident, $row: expr) => { assert!($tables.$table().row($row).is_none()); + assert!($tables.$table().row_view($row).is_none()); assert_eq!($tables.$table().num_rows(), 0); assert_eq!($tables.$table().iter().count(), 0); assert_eq!($tables.$table_iter().count(), 0); @@ -43,6 +44,10 @@ mod test_adding_rows_without_metadata { // are held in an Option. match tables.$table().row(id) { Some(row) => { + match tables.$table().row_view(id) { + Some(view) => assert_eq!(view, row), + None => panic!("if there is a row, there must be a row view") + } assert!(row.metadata.is_none()); // A row equals itself @@ -273,7 +278,13 @@ mod test_metadata_round_trips { assert_eq!($tables.$table().num_rows(), 1); match $tables.$table().row(id) { - Some(row) => assert!(row.metadata.is_some()), + Some(row) => { + assert!(row.metadata.is_some()); + match $tables.$table().row_view(id) { + Some(view) => assert_eq!(row, view), + None => panic!("if there is a row, there must be a view!"), + } + } None => panic!("Expected Some(row) from {} table", stringify!(table)), }