use std::borrow::Cow;
use std::collections::HashMap;
use crate::datatypes::Value;
use crate::graph::schema::{InternedKey, NodeData, NodeInfo, PropertyStorage, StringInterner};
use crate::graph::storage::column_store::ColumnStore;
#[derive(Clone, Copy)]
pub struct NodeView<'a> {
data: &'a NodeData,
store: Option<(&'a ColumnStore, u32)>,
}
impl<'a> NodeView<'a> {
#[inline]
pub(crate) fn new(data: &'a NodeData, store: Option<(&'a ColumnStore, u32)>) -> Self {
NodeView { data, store }
}
#[inline]
pub fn data(&self) -> &'a NodeData {
self.data
}
#[inline]
pub fn node_type(&self) -> InternedKey {
self.data.node_type
}
#[inline]
pub fn node_type_str<'i>(&self, interner: &'i StringInterner) -> &'i str {
interner.resolve(self.data.node_type)
}
#[inline]
pub fn get_node_type_ref<'i>(&self, interner: &'i StringInterner) -> &'i str {
interner.resolve(self.data.node_type)
}
#[inline]
pub fn id(&self) -> Cow<'a, Value> {
if matches!(self.data.id, Value::Null) {
if let Some((store, row_id)) = self.store {
if let Some(v) = store.get_id(row_id) {
return Cow::Owned(v);
}
}
}
Cow::Borrowed(&self.data.id)
}
#[inline]
pub fn title(&self) -> Cow<'a, Value> {
if matches!(self.data.title, Value::Null) {
if let Some((store, row_id)) = self.store {
if let Some(v) = store.get_title(row_id) {
return Cow::Owned(v);
}
}
}
Cow::Borrowed(&self.data.title)
}
#[inline]
pub fn get(&self, key: InternedKey) -> Option<Cow<'a, Value>> {
match self.store {
Some((store, row_id)) => store.get(row_id, key).map(Cow::Owned),
None => self.data.properties.get(key),
}
}
#[inline]
pub fn get_value(&self, key: InternedKey) -> Option<Value> {
match self.store {
Some((store, row_id)) => store.get(row_id, key),
None => self.data.properties.get_value(key),
}
}
#[inline]
pub fn get_property(&self, key: &str) -> Option<Cow<'a, Value>> {
self.get(InternedKey::from_str(key))
}
#[inline]
pub fn get_property_value(&self, key: &str) -> Option<Value> {
self.get_value(InternedKey::from_str(key))
}
#[inline]
pub fn get_field_ref(&self, field: &str) -> Option<Cow<'a, Value>> {
match field {
"id" => Some(self.id()),
"title" => Some(self.title()),
_ => self.get(InternedKey::from_str(field)),
}
}
#[inline]
pub fn resolved_field(
&self,
type_str: &str,
field: &str,
key: InternedKey,
) -> Option<Cow<'a, Value>> {
if field == "id" {
return Some(self.id());
}
if field == "title" {
return Some(self.title());
}
if let Some(value) = self.get(key) {
return Some(value);
}
crate::graph::schema::soft_alias_fallback(field).map(|fallback| match fallback {
crate::graph::schema::SoftAliasFallback::Title => self.title(),
crate::graph::schema::SoftAliasFallback::TypeString => {
Cow::Owned(Value::String(type_str.to_string()))
}
})
}
#[inline]
pub fn contains(&self, key: InternedKey) -> bool {
match self.store {
Some((store, row_id)) => store.get(row_id, key).is_some(),
None => self.data.properties.contains(key),
}
}
#[inline]
pub fn has_property(&self, key: &str) -> bool {
self.contains(InternedKey::from_str(key))
}
#[inline]
pub fn properties_are_columnar(&self) -> bool {
self.store.is_some()
}
#[inline]
pub fn property_count(&self) -> usize {
match self.store {
Some((store, row_id)) => store.row_properties(row_id).len(),
None => self.data.properties.len(),
}
}
#[inline]
pub fn str_prop_eq(&self, key: InternedKey, target: &str) -> Option<bool> {
match self.store {
Some((store, row_id)) => store.str_prop_eq(row_id, key, target),
None => self.data.properties.str_prop_eq(key, target),
}
}
pub fn field_contains_ci(&self, field: &str, needle_lower: &str) -> bool {
self.get_field_ref(field)
.and_then(|v| match &*v {
Value::String(s) => Some(s.to_lowercase().contains(needle_lower)),
_ => None,
})
.unwrap_or(false)
}
pub fn field_starts_with_ci(&self, field: &str, prefix_lower: &str) -> bool {
self.get_field_ref(field)
.and_then(|v| match &*v {
Value::String(s) => Some(s.to_lowercase().starts_with(prefix_lower)),
_ => None,
})
.unwrap_or(false)
}
pub fn property_pairs(&self) -> Vec<(InternedKey, Value)> {
match self.store {
Some((store, row_id)) => store.row_properties(row_id),
None => match &self.data.properties {
PropertyStorage::Map(map) => map.iter().map(|(k, v)| (*k, v.clone())).collect(),
PropertyStorage::Compact { schema, values } => schema
.slots
.iter()
.enumerate()
.filter_map(|(i, ik)| {
values.get(i).and_then(|v| {
if matches!(v, Value::Null) {
None
} else {
Some((*ik, v.clone()))
}
})
})
.collect(),
PropertyStorage::Columnar(_) => unreachable!("store resolved above"),
},
}
}
pub fn property_keys(&self, interner: &'a StringInterner) -> Vec<&'a str> {
match self.store {
Some((store, row_id)) => store
.row_properties(row_id)
.into_iter()
.filter_map(|(ik, _)| interner.try_resolve(ik))
.collect(),
None => self.data.properties.keys(interner).collect(),
}
}
pub fn property_pairs_named(&self, interner: &StringInterner) -> Vec<(String, Value)> {
self.property_pairs()
.into_iter()
.filter_map(|(ik, v)| interner.try_resolve(ik).map(|s| (s.to_string(), v)))
.collect()
}
#[inline]
pub fn properties_cloned(&self, interner: &StringInterner) -> HashMap<String, Value> {
self.property_pairs_named(interner).into_iter().collect()
}
#[inline]
pub fn to_node_info(&self, interner: &StringInterner) -> NodeInfo {
NodeInfo {
id: self.id().into_owned(),
title: self.title().into_owned(),
node_type: self.node_type_str(interner).to_string(),
properties: self.properties_cloned(interner),
}
}
}
impl std::fmt::Debug for NodeView<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeView")
.field("id", &self.data.id)
.field("title", &self.data.title)
.field("node_type", &self.data.node_type)
.field("columnar_row", &self.store.map(|(_, r)| r))
.finish()
}
}