use grafeo_common::types::{NodeId, PropertyKey, Value};
use grafeo_common::utils::hash::FxHashMap;
use super::column::ColumnCodec;
use super::id::encode_node_id;
use super::schema::TableSchema;
use super::zone_map::ZoneMap;
#[derive(Debug)]
pub struct NodeTable {
schema: TableSchema,
columns: FxHashMap<PropertyKey, ColumnCodec>,
zone_maps: FxHashMap<PropertyKey, ZoneMap>,
len: usize,
}
impl NodeTable {
#[must_use]
pub fn new(schema: TableSchema) -> Self {
Self {
schema,
columns: FxHashMap::default(),
zone_maps: FxHashMap::default(),
len: 0,
}
}
#[must_use]
pub fn from_columns(
schema: TableSchema,
columns: FxHashMap<PropertyKey, ColumnCodec>,
zone_maps: FxHashMap<PropertyKey, ZoneMap>,
len: usize,
) -> Self {
Self {
schema,
columns,
zone_maps,
len,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn table_id(&self) -> u16 {
self.schema.table_id
}
#[must_use]
pub fn label(&self) -> &str {
self.schema.label.as_str()
}
#[must_use]
pub fn node_ids(&self) -> Vec<NodeId> {
let table_id = self.schema.table_id;
(0..self.len)
.map(|offset| encode_node_id(table_id, offset as u64))
.collect()
}
#[must_use]
pub fn get_property(&self, offset: usize, key: &PropertyKey) -> Option<Value> {
self.columns.get(key)?.get(offset)
}
#[must_use]
pub fn get_all_properties(&self, offset: usize) -> FxHashMap<PropertyKey, Value> {
let mut props = FxHashMap::default();
if offset >= self.len {
return props;
}
for (key, col) in &self.columns {
if let Some(value) = col.get(offset) {
props.insert(key.clone(), value);
}
}
props
}
#[must_use]
pub fn get_raw_u64(&self, offset: usize, key: &PropertyKey) -> Option<u64> {
self.columns.get(key)?.get_raw_u64(offset)
}
#[must_use]
pub fn zone_map(&self, key: &PropertyKey) -> Option<&ZoneMap> {
self.zone_maps.get(key)
}
#[must_use]
pub fn column(&self, key: &PropertyKey) -> Option<&ColumnCodec> {
self.columns.get(key)
}
#[must_use]
pub fn property_keys(&self) -> Vec<PropertyKey> {
self.columns.keys().cloned().collect()
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.columns.values().map(|c| c.heap_bytes()).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::BitPackedInts;
use crate::graph::compact::id::decode_node_id;
use crate::graph::compact::schema::{ColumnDef, ColumnType};
fn sample_table() -> NodeTable {
let schema = TableSchema::new(
"Movie",
3,
vec![
ColumnDef::new("rating", ColumnType::UInt { bits: 4 }),
ColumnDef::new("count", ColumnType::UInt { bits: 32 }),
],
);
let ratings = vec![1u64, 5, 10, 15, 3];
let counts = vec![100u64, 200, 300, 400, 500];
let mut columns = FxHashMap::default();
columns.insert(
PropertyKey::new("rating"),
ColumnCodec::BitPacked(BitPackedInts::pack(&ratings)),
);
columns.insert(
PropertyKey::new("count"),
ColumnCodec::BitPacked(BitPackedInts::pack(&counts)),
);
let mut zone_maps = FxHashMap::default();
zone_maps.insert(
PropertyKey::new("rating"),
ZoneMap {
min: Some(Value::Int64(1)),
max: Some(Value::Int64(15)),
null_count: 0,
row_count: 5,
},
);
zone_maps.insert(
PropertyKey::new("count"),
ZoneMap {
min: Some(Value::Int64(100)),
max: Some(Value::Int64(500)),
null_count: 0,
row_count: 5,
},
);
NodeTable::from_columns(schema, columns, zone_maps, 5)
}
#[test]
fn test_len_and_label() {
let table = sample_table();
assert_eq!(table.len(), 5);
assert!(!table.is_empty());
assert_eq!(table.table_id(), 3);
assert_eq!(table.label(), "Movie");
}
#[test]
fn test_empty_table() {
let schema = TableSchema::new("Empty", 0, vec![]);
let table = NodeTable::new(schema);
assert_eq!(table.len(), 0);
assert!(table.is_empty());
assert!(table.node_ids().is_empty());
}
#[test]
fn test_node_ids() {
let table = sample_table();
let ids = table.node_ids();
assert_eq!(ids.len(), 5);
for (i, id) in ids.iter().enumerate() {
let (tid, offset) = decode_node_id(*id);
assert_eq!(tid, 3);
assert_eq!(offset, i as u64);
}
}
#[test]
fn test_get_property() {
let table = sample_table();
assert_eq!(
table.get_property(0, &PropertyKey::new("rating")),
Some(Value::Int64(1))
);
assert_eq!(
table.get_property(0, &PropertyKey::new("count")),
Some(Value::Int64(100))
);
assert_eq!(
table.get_property(4, &PropertyKey::new("rating")),
Some(Value::Int64(3))
);
assert_eq!(
table.get_property(4, &PropertyKey::new("count")),
Some(Value::Int64(500))
);
}
#[test]
fn test_get_all_properties() {
let table = sample_table();
let props = table.get_all_properties(2);
assert_eq!(props.len(), 2);
assert_eq!(props[&PropertyKey::new("rating")], Value::Int64(10));
assert_eq!(props[&PropertyKey::new("count")], Value::Int64(300));
}
#[test]
fn test_get_raw_u64() {
let table = sample_table();
assert_eq!(table.get_raw_u64(0, &PropertyKey::new("count")), Some(100));
assert_eq!(table.get_raw_u64(3, &PropertyKey::new("count")), Some(400));
assert_eq!(table.get_raw_u64(4, &PropertyKey::new("rating")), Some(3));
}
#[test]
fn test_out_of_bounds_returns_none() {
let table = sample_table();
assert_eq!(table.get_property(5, &PropertyKey::new("rating")), None);
assert_eq!(table.get_property(999, &PropertyKey::new("count")), None);
assert_eq!(table.get_raw_u64(5, &PropertyKey::new("count")), None);
assert_eq!(table.get_property(0, &PropertyKey::new("missing")), None);
assert_eq!(table.get_raw_u64(0, &PropertyKey::new("missing")), None);
let props = table.get_all_properties(100);
assert!(props.is_empty());
}
#[test]
fn test_zone_map_lookup() {
let table = sample_table();
let zm = table.zone_map(&PropertyKey::new("rating")).unwrap();
assert_eq!(zm.min, Some(Value::Int64(1)));
assert_eq!(zm.max, Some(Value::Int64(15)));
assert_eq!(zm.row_count, 5);
assert!(table.zone_map(&PropertyKey::new("missing")).is_none());
}
#[test]
fn test_column_lookup() {
let table = sample_table();
assert!(table.column(&PropertyKey::new("rating")).is_some());
assert!(table.column(&PropertyKey::new("count")).is_some());
assert!(table.column(&PropertyKey::new("missing")).is_none());
}
#[test]
fn test_property_keys() {
let table = sample_table();
let mut keys = table.property_keys();
keys.sort_by(|a, b| a.as_ref().cmp(b.as_ref()));
assert_eq!(keys.len(), 2);
assert_eq!(keys[0].as_ref(), "count");
assert_eq!(keys[1].as_ref(), "rating");
}
}