use super::{QueryError, QueryList, QueryValue, ValueContext};
use crate::property_graph::{MAX_GRAPH_INPUT_BYTES, PropertyData, PropertyValue};
pub enum PropertyScratch<'a> {
None,
Strings(&'a mut [&'a str]),
Bools(&'a mut [bool]),
Integers(&'a mut [i64]),
Floats(&'a mut [f64]),
}
#[derive(Clone, Copy, Debug)]
pub struct PropertyAssignment<'a> {
data: Option<PropertyData<'a>>,
bytes: usize,
}
impl<'a> PropertyAssignment<'a> {
pub const fn data(self) -> Option<PropertyData<'a>> {
self.data
}
pub const fn payload_bytes(self) -> usize {
self.bytes
}
}
impl<'a> QueryValue<'a> {
pub fn from_property(
value: Option<PropertyValue<'a>>,
context: &mut ValueContext<'_>,
) -> Result<Self, QueryError> {
context.step()?;
let Some(value) = value else {
return Ok(Self::Null);
};
Ok(match value.data() {
PropertyData::String(value) => Self::String(value),
PropertyData::Bool(value) => Self::Bool(value),
PropertyData::I64(value) => Self::I64(value),
PropertyData::F64(value) => Self::F64(value),
_ => Self::List(QueryList::property(value)?),
})
}
pub fn to_property<'out>(
self,
scratch: PropertyScratch<'out>,
context: &mut ValueContext<'_>,
) -> Result<PropertyAssignment<'out>, QueryError>
where
'a: 'out,
{
context.step()?;
self.validate(context)?;
let mut bytes = 0_usize;
let data = match self {
Self::Null => None,
Self::String(value) => {
bytes = value.len();
Some(PropertyData::String(value))
}
Self::Bool(value) => {
bytes = 1;
Some(PropertyData::Bool(value))
}
Self::I64(value) => {
bytes = 8;
Some(PropertyData::I64(value))
}
Self::F64(value) => {
bytes = 8;
Some(PropertyData::F64(value))
}
Self::Map(_) | Self::NodeRef(_) | Self::RelRef(_) => return Err(QueryError::Type),
Self::List(list) if list.is_empty() => Some(PropertyData::EmptyList { count: 0 }),
Self::List(list) => {
let tag = scalar_tag(list.get(0).ok_or(QueryError::Type)?)?;
for index in 0..list.len() {
context.step()?;
let value = list.get(index).ok_or(QueryError::Type)?;
if scalar_tag(value)? != tag {
return Err(QueryError::Type);
}
let size = match value {
Self::String(s) => s.len(),
Self::Bool(_) => 1,
_ => 8,
};
bytes = bytes.checked_add(size).ok_or(QueryError::PropertyLimit)?;
if bytes > MAX_GRAPH_INPUT_BYTES {
return Err(QueryError::PropertyLimit);
}
}
Some(match (tag, scratch) {
(0, PropertyScratch::Strings(output)) => {
PropertyData::Strings(copy(list, output, context, |value| match value {
Self::String(v) => Ok(v),
_ => Err(QueryError::Type),
})?)
}
(1, PropertyScratch::Bools(output)) => {
PropertyData::Bools(copy(list, output, context, |value| match value {
Self::Bool(v) => Ok(v),
_ => Err(QueryError::Type),
})?)
}
(2, PropertyScratch::Integers(output)) => {
PropertyData::Integers(copy(list, output, context, |value| match value {
Self::I64(v) => Ok(v),
_ => Err(QueryError::Type),
})?)
}
(3, PropertyScratch::Floats(output)) => {
PropertyData::Floats(copy(list, output, context, |value| match value {
Self::F64(v) => Ok(v),
_ => Err(QueryError::Type),
})?)
}
_ => return Err(QueryError::Type),
})
}
};
if bytes > MAX_GRAPH_INPUT_BYTES {
return Err(QueryError::PropertyLimit);
}
Ok(PropertyAssignment { data, bytes })
}
}
fn scalar_tag(value: QueryValue<'_>) -> Result<u8, QueryError> {
match value {
QueryValue::String(_) => Ok(0),
QueryValue::Bool(_) => Ok(1),
QueryValue::I64(_) => Ok(2),
QueryValue::F64(_) => Ok(3),
_ => Err(QueryError::Type),
}
}
fn copy<'out, 'value: 'out, T>(
list: QueryList<'value>,
output: &'out mut [T],
context: &mut ValueContext<'_>,
extract: impl Fn(QueryValue<'value>) -> Result<T, QueryError>,
) -> Result<&'out [T], QueryError> {
let target = output
.get_mut(..list.len())
.ok_or(QueryError::BufferTooSmall)?;
for (index, slot) in target.iter_mut().enumerate() {
context.step()?;
*slot = extract(list.get(index).ok_or(QueryError::Type)?)?;
}
Ok(target)
}