use super::context::MAX_QUERY_BYTES;
use super::{QueryError, QueryValue, QueryView, ValueContext};
use crate::property_graph::{NodeId, PropertyData, PropertyValue, RelId};
pub const MAX_LIST_ELEMENTS: usize = 524_288;
pub const MAX_LIST_DEPTH: u8 = 16;
#[derive(Clone, Copy, Debug)]
pub struct QueryList<'a> {
values: Backing<'a>,
view: Option<&'a QueryView>,
elements: usize,
depth: u8,
bytes: usize,
}
#[derive(Clone, Copy, Debug)]
pub(super) enum Backing<'a> {
Values(&'a [QueryValue<'a>]),
Nodes(&'a [NodeId]),
Relationships(&'a [RelId]),
Strings(&'a [&'a str]),
Bools(&'a [bool]),
Integers(&'a [i64]),
Floats(&'a [f64]),
Arena {
source: &'a dyn ListArena,
start: usize,
len: usize,
},
}
pub(super) trait ListArena: std::fmt::Debug {
fn value(&self, index: usize) -> Option<QueryValue<'_>>;
}
impl<'a> QueryList<'a> {
pub(super) const fn backing(self) -> Backing<'a> {
self.values
}
pub(super) fn arena_descriptor(
self,
expected: &dyn ListArena,
) -> Option<(usize, usize, usize, u8, usize, bool)> {
let Backing::Arena { source, start, len } = self.values else {
return None;
};
if !std::ptr::eq(source, expected) {
return None;
}
Some((
start,
len,
self.elements,
self.depth,
self.bytes,
self.view.is_some(),
))
}
pub(super) fn arena(
source: &'a dyn ListArena,
start: usize,
len: usize,
elements: usize,
depth: u8,
bytes: usize,
view: Option<&'a QueryView>,
) -> Self {
Self {
values: Backing::Arena { source, start, len },
view,
elements,
depth,
bytes,
}
}
pub(super) fn copied_nodes(view: &'a QueryView, ids: &'a [NodeId]) -> Self {
Self {
values: Backing::Nodes(ids),
view: Some(view),
elements: ids.len(),
depth: 1,
bytes: std::mem::size_of_val(ids),
}
}
pub(super) fn copied_relationships(view: &'a QueryView, ids: &'a [RelId]) -> Self {
Self {
values: Backing::Relationships(ids),
view: Some(view),
elements: ids.len(),
depth: 1,
bytes: std::mem::size_of_val(ids),
}
}
pub(super) const fn node_ids(self) -> Option<&'a [NodeId]> {
if let Backing::Nodes(v) = self.values {
Some(v)
} else {
None
}
}
pub(super) const fn relationship_ids(self) -> Option<&'a [RelId]> {
if let Backing::Relationships(v) = self.values {
Some(v)
} else {
None
}
}
pub fn new(
values: &'a [QueryValue<'a>],
context: &mut ValueContext<'_>,
) -> Result<Self, QueryError> {
let mut view = None;
let mut elements = values.len();
let mut depth = 1;
let mut bytes = std::mem::size_of_val(values);
if elements > MAX_LIST_ELEMENTS || bytes > MAX_QUERY_BYTES {
return Err(QueryError::ListLimit);
}
context.checkpoint()?;
for value in values {
context.step()?;
value.validate(context)?;
if let Some(owner) = value.view() {
view = Some(owner);
}
match value {
QueryValue::Map(super::QueryMap { entries: list }) | QueryValue::List(list) => {
elements = elements
.checked_add(list.elements)
.ok_or(QueryError::ListLimit)?;
depth = depth.max(list.depth + 1);
bytes = bytes.checked_add(list.bytes).ok_or(QueryError::ListLimit)?;
}
QueryValue::String(text) => {
bytes = bytes.checked_add(text.len()).ok_or(QueryError::ListLimit)?
}
_ => {}
}
if elements > MAX_LIST_ELEMENTS || depth > MAX_LIST_DEPTH || bytes > MAX_QUERY_BYTES {
return Err(QueryError::ListLimit);
}
}
Ok(Self {
values: Backing::Values(values),
view,
elements,
depth,
bytes,
})
}
pub(super) fn property(value: PropertyValue<'a>) -> Result<Self, QueryError> {
let (values, descriptors) = match value.data() {
PropertyData::EmptyList { count: 0 } => (Backing::Values(&[]), 0),
PropertyData::Strings(v) => (Backing::Strings(v), std::mem::size_of_val(v)),
PropertyData::Bools(v) => (Backing::Bools(v), 0),
PropertyData::Integers(v) => (Backing::Integers(v), 0),
PropertyData::Floats(v) => (Backing::Floats(v), 0),
_ => return Err(QueryError::Type),
};
let bytes = descriptors
.checked_add(value.payload_bytes())
.ok_or(QueryError::ListLimit)?;
if bytes > MAX_QUERY_BYTES {
return Err(QueryError::ListLimit);
}
Ok(Self {
values,
view: None,
elements: value.list_len().ok_or(QueryError::Type)?,
depth: 1,
bytes,
})
}
pub fn nodes(
view: &'a QueryView,
ids: &'a [NodeId],
context: &mut ValueContext<'_>,
) -> Result<Self, QueryError> {
Self::packed(
view,
Backing::Nodes(ids),
ids.len(),
std::mem::size_of_val(ids),
context,
)
}
pub fn relationships(
view: &'a QueryView,
ids: &'a [RelId],
context: &mut ValueContext<'_>,
) -> Result<Self, QueryError> {
Self::packed(
view,
Backing::Relationships(ids),
ids.len(),
std::mem::size_of_val(ids),
context,
)
}
fn packed(
view: &'a QueryView,
values: Backing<'a>,
elements: usize,
bytes: usize,
context: &mut ValueContext<'_>,
) -> Result<Self, QueryError> {
context.step()?;
if !std::ptr::eq(view, context.view) {
return Err(QueryError::ForeignView);
}
if elements > MAX_LIST_ELEMENTS || bytes > MAX_QUERY_BYTES {
return Err(QueryError::ListLimit);
}
Ok(Self {
values,
view: Some(view),
elements,
depth: 1,
bytes,
})
}
pub(super) const fn view(self) -> Option<&'a QueryView> {
self.view
}
pub const fn len(self) -> usize {
match self.values {
Backing::Values(v) => v.len(),
Backing::Nodes(v) => v.len(),
Backing::Relationships(v) => v.len(),
Backing::Strings(v) => v.len(),
Backing::Bools(v) => v.len(),
Backing::Integers(v) => v.len(),
Backing::Floats(v) => v.len(),
Backing::Arena { len, .. } => len,
}
}
pub const fn is_empty(self) -> bool {
self.len() == 0
}
pub const fn elements(self) -> usize {
self.elements
}
pub const fn depth(self) -> u8 {
self.depth
}
pub const fn borrowed_bytes(self) -> usize {
self.bytes
}
pub fn get(self, index: usize) -> Option<QueryValue<'a>> {
match self.values {
Backing::Values(values) => values.get(index).copied(),
Backing::Strings(values) => values.get(index).map(|value| QueryValue::String(value)),
Backing::Bools(values) => values.get(index).copied().map(QueryValue::Bool),
Backing::Integers(values) => values.get(index).copied().map(QueryValue::I64),
Backing::Floats(values) => values.get(index).copied().map(QueryValue::F64),
Backing::Nodes(ids) => Some(self.view?.node(*ids.get(index)?)),
Backing::Relationships(ids) => Some(self.view?.relationship(*ids.get(index)?)),
Backing::Arena { source, start, len } => {
if index < len {
source.value(start.checked_add(index)?)
} else {
None
}
}
}
}
}