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;
use crate::graph::storage::StrField;
#[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 resolved_field_str(
&self,
type_str: &'a str,
field: &str,
key: InternedKey,
) -> StrField<'a> {
if field == "id" {
return self.id_field();
}
if field == "title" {
return self.title_field();
}
match self.str_field(key) {
StrField::Absent => {}
resolved => return resolved,
}
match crate::graph::schema::soft_alias_fallback(field) {
Some(crate::graph::schema::SoftAliasFallback::Title) => self.title_field(),
Some(crate::graph::schema::SoftAliasFallback::TypeString) => {
StrField::Str(Cow::Borrowed(type_str))
}
None => StrField::Absent,
}
}
#[inline]
pub fn str_field(&self, key: InternedKey) -> StrField<'a> {
match self.store {
Some((store, row_id)) => store.str_field(row_id, key),
None => self.data.properties.str_field(key),
}
}
#[inline]
pub fn title_field(&self) -> StrField<'a> {
Self::identity_str(
&self.data.title,
|store, row_id| store.title_field(row_id),
self.store,
)
}
#[inline]
pub fn id_field(&self) -> StrField<'a> {
Self::identity_str(
&self.data.id,
|store, row_id| store.id_field(row_id),
self.store,
)
}
#[inline]
fn identity_str(
inline: &'a Value,
from_store: impl FnOnce(&'a ColumnStore, u32) -> StrField<'a>,
store: Option<(&'a ColumnStore, u32)>,
) -> StrField<'a> {
match inline {
Value::String(s) => StrField::Str(Cow::Borrowed(s.as_str())),
Value::Null => match store {
Some((store, row_id)) => from_store(store, row_id),
None => StrField::Absent,
},
_ => StrField::NotString,
}
}
#[inline]
pub fn contains(&self, key: InternedKey) -> bool {
match self.store {
Some((store, row_id)) => store.contains_value(row_id, key),
None => self.data.properties.contains(key),
}
}
#[inline]
pub fn has_property(&self, key: &str) -> bool {
self.contains(InternedKey::from_str(key))
}
#[inline]
pub(crate) 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_property_count(row_id),
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::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_property_keys(row_id)
.into_iter()
.filter_map(|ik| interner.try_resolve(ik))
.collect(),
None => self.data.properties.keys(interner).collect(),
}
}
pub fn property_key_set(&self) -> Vec<InternedKey> {
match self.store {
Some((store, row_id)) => store.row_property_keys(row_id),
None => match &self.data.properties {
PropertyStorage::Map(map) => map.keys().copied().collect(),
PropertyStorage::Columnar(_) => unreachable!("store resolved above"),
},
}
}
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()
}
}