mod expression;
mod lineage;
mod mutation;
mod parameters;
mod search;
pub use search::{SearchBounds, SearchMode, SearchOptions};
mod accounting;
mod validate;
use super::{Arithmetic, Comparison, QueryError, StringPredicate, ValueContext};
use crate::property_graph::{EntityKind, GraphName, NodeId, RelId};
pub use accounting::{PlanBacking, RetainedRegion};
pub const MAX_PLAN_NODES: usize = 4096;
pub const MAX_PLAN_DEPTH: usize = 64;
pub const MAX_COLUMNS: usize = 256;
pub const VALIDATION_SCRATCH_BYTES: usize = 64 * 1024;
macro_rules! id {
($name:ident, $doc:literal) => {
#[doc = $doc]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct $name(pub u32);
};
}
id!(PlanNodeId, "Index into the operator arena.");
id!(ExprId, "Index into the expression arena.");
id!(SlotId, "Logical slot identity; it is not a row offset.");
id!(ParameterId, "Index into the declared parameter arena.");
id!(
PatternId,
"Relationship-uniqueness scope for one MATCH pattern."
);
id!(SearchCallId, "Source-order search call identity.");
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ValueKinds(u16);
impl ValueKinds {
pub const NULL: Self = Self(1);
pub const BOOL: Self = Self(2);
pub const I64: Self = Self(4);
pub const F64: Self = Self(8);
pub const STRING: Self = Self(16);
pub const NODE: Self = Self(32);
pub const REL: Self = Self(64);
pub const LIST: Self = Self(128);
pub const ANY: Self = Self(511);
pub const MAP: Self = Self(256);
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub(super) const fn overlaps(self, other: Self) -> bool {
self.0 & other.0 != 0
}
}
#[derive(Clone, Copy, Debug)]
pub enum Literal<'a> {
Null,
Bool(bool),
I64(i64),
F64(f64),
String(&'a str),
}
#[derive(Clone, Copy, Debug)]
pub enum Expression<'a> {
Aggregate {
operation: AggregateExpression,
operand: Option<ExprId>,
},
List(&'a [ExprId]),
Map(&'a [ExprId]),
Local(SlotId),
Comprehension {
variable: SlotId,
source: ExprId,
predicate: Option<ExprId>,
projection: Option<ExprId>,
},
Coalesce(&'a [ExprId]),
Case {
operand: Option<ExprId>,
alternatives: &'a [ExprId],
otherwise: ExprId,
},
Property {
entity: ExprId,
name: GraphName<'a>,
},
HasLabel {
entity: ExprId,
label: GraphName<'a>,
},
Binary {
operation: BinaryExpression,
left: ExprId,
right: ExprId,
},
Unary {
operation: UnaryExpression,
operand: ExprId,
},
Literal(Literal<'a>),
Slot(SlotId),
Parameter(ParameterId),
}
#[derive(Clone, Copy, Debug)]
pub enum UnaryExpression {
Not,
Positive,
Negate,
IsNull,
IsNotNull,
Size,
ToInteger,
ToFloat,
Floor,
Round,
Head,
Last,
Abs,
Labels,
RelType,
StoredText,
NodeIdText,
RelIdText,
}
#[derive(Clone, Copy, Debug)]
pub enum BinaryExpression {
And,
Or,
Xor,
Comparison(Comparison),
Arithmetic(Arithmetic),
String(StringPredicate),
In,
Index,
}
#[derive(Clone, Copy, Debug)]
pub enum AggregateExpression {
Avg {
distinct: bool,
},
Sum {
distinct: bool,
},
Min,
Max,
Count {
distinct: bool,
},
Collect {
distinct: bool,
},
}
#[derive(Clone, Copy, Debug)]
pub enum SearchRequest {
Vector {
vector: ExprId,
k: ExprId,
mode: SearchMode,
eligible: Option<ExprId>,
options: SearchOptions,
},
Hybrid {
vector: ExprId,
text: ExprId,
k: ExprId,
mode: SearchMode,
eligible: Option<ExprId>,
options: SearchOptions,
},
Text {
query: ExprId,
k: ExprId,
eligible: Option<ExprId>,
options: SearchOptions,
},
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SearchOutputs {
pub node: Option<SlotId>,
pub distance: Option<SlotId>,
pub score: Option<SlotId>,
pub vector_distance: Option<SlotId>,
pub lexical_score: Option<SlotId>,
}
#[derive(Clone, Copy, Debug)]
pub struct ParameterBinding<'a> {
pub name: &'a str,
pub value: super::QueryValue<'a>,
}
#[derive(Clone, Copy, Debug)]
pub struct Projection {
pub slot: SlotId,
pub expression: ExprId,
}
#[derive(Clone, Copy, Debug)]
pub struct Parameter<'a> {
pub name: &'a str,
pub kinds: ValueKinds,
}
#[derive(Clone, Copy, Debug)]
pub enum OperatorKind<'a> {
Unit,
Join {
predicate: Option<ExprId>,
},
Distinct,
Sort(&'a [SortKey]),
ScanNodes {
output: SlotId,
label: Option<GraphName<'a>>,
},
Eager,
Mutate(&'a [Mutation<'a>]),
Aggregate {
keys: &'a [Projection],
aggregates: &'a [Projection],
},
Search {
call: SearchCallId,
request: SearchRequest,
outputs: SearchOutputs,
},
OffsetLimit {
offset: u64,
limit: Option<u64>,
},
LookupNode {
output: SlotId,
id: NodeId,
},
LookupRelationship {
output: SlotId,
id: RelId,
},
LookupKey {
output: SlotId,
namespace: GraphName<'a>,
key: ExprId,
kind: EntityKind,
},
Expand {
source: SlotId,
node: SlotId,
relationship: SlotId,
direction: Direction,
relationship_types: &'a [GraphName<'a>],
pattern: PatternId,
},
BoundedExpand {
source: SlotId,
node: SlotId,
relationships: SlotId,
edge_predicate: Option<EdgePredicate>,
completed_edge_predicate: Option<CompletedEdgePredicate>,
min: u8,
max: u8,
direction: Direction,
relationship_types: &'a [GraphName<'a>],
pattern: PatternId,
},
OptionalApply {
predicate: Option<ExprId>,
},
Project(&'a [Projection]),
With(&'a [Projection]),
Filter(ExprId),
Collect,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EdgePredicate {
pub current_edge: SlotId,
pub expression: ExprId,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CompletedEdgePredicate {
pub current_edge: SlotId,
pub expression: ExprId,
}
#[derive(Clone, Copy, Debug)]
pub struct SortKey {
pub expression: ExprId,
pub descending: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u16)]
pub enum Barrier {
Optional = 1,
Scope = 2,
Aggregate = 4,
Distinct = 8,
Sort = 16,
Limit = 32,
Eager = 64,
Search = 128,
}
#[derive(Clone, Copy, Debug)]
pub enum Mutation<'a> {
CreateNode {
output: SlotId,
labels: &'a [GraphName<'a>],
},
CreateRelationship {
output: SlotId,
source: ExprId,
target: ExprId,
relationship_type: GraphName<'a>,
},
RemoveProperty {
entity: ExprId,
name: GraphName<'a>,
},
SetLabel {
entity: ExprId,
label: GraphName<'a>,
present: bool,
},
Delete {
entity: ExprId,
detach: bool,
},
SetProperty {
entity: ExprId,
name: GraphName<'a>,
value: ExprId,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Direction {
Outgoing,
Incoming,
Either,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Classification(u8);
impl Classification {
pub const fn reads(self) -> bool {
self.0 & 1 != 0
}
pub const fn writes(self) -> bool {
self.0 & 2 != 0
}
pub const fn searches(self) -> bool {
self.0 & 4 != 0
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Barriers(u16);
impl Barriers {
pub const fn contains(self, barrier: Barrier) -> bool {
self.0 & barrier as u16 != 0
}
pub const fn optional(self) -> bool {
self.0 & 1 != 0
}
pub const fn scope(self) -> bool {
self.0 & 2 != 0
}
}
#[derive(Clone, Copy, Debug)]
pub struct Operator<'a> {
pub inputs: &'a [PlanNodeId],
pub kind: OperatorKind<'a>,
}
#[derive(Clone, Copy, Debug)]
pub struct PlanDescription<'a> {
pub operators: &'a [Operator<'a>],
pub expressions: &'a [Expression<'a>],
pub parameters: &'a [Parameter<'a>],
pub root: PlanNodeId,
pub eager_searches: &'a [PlanNodeId],
}
#[derive(Clone, Copy, Debug)]
pub struct PlanFootprint {
retained_bytes: usize,
}
impl PlanFootprint {
pub const fn declared(retained_bytes: usize) -> Self {
Self { retained_bytes }
}
pub const fn retained_bytes(self) -> usize {
self.retained_bytes
}
}
#[derive(Clone, Copy, Debug, Default)]
struct Slot {
id: u32,
kinds: ValueKinds,
}
#[derive(Clone, Debug)]
pub struct NodeFacts {
state: u8,
slots: [Slot; MAX_COLUMNS],
width: usize,
depth: usize,
classification: Classification,
barriers: Barriers,
singleton: bool,
ordered: bool,
search_calls: u8,
}
impl Default for NodeFacts {
fn default() -> Self {
Self {
state: 0,
slots: [Slot::default(); MAX_COLUMNS],
width: 0,
depth: 0,
classification: Classification::default(),
barriers: Barriers::default(),
singleton: false,
ordered: false,
search_calls: 0,
}
}
}
impl NodeFacts {
pub const fn ordered(&self) -> bool {
self.ordered
}
pub const fn singleton(&self) -> bool {
self.singleton
}
pub const fn classification(&self) -> Classification {
self.classification
}
pub const fn barriers(&self) -> Barriers {
self.barriers
}
pub const fn width(&self) -> usize {
self.width
}
pub fn slot_at(&self, ordinal: usize) -> Option<(SlotId, ValueKinds)> {
let slot = self.slots.get(..self.width)?.get(ordinal)?;
Some((SlotId(slot.id), slot.kinds))
}
pub fn slot(&self, id: SlotId) -> Option<ValueKinds> {
self.slots
.get(..self.width)?
.iter()
.find(|slot| slot.id == id.0)
.map(|slot| slot.kinds)
}
}
pub struct GraphPlan<'a, 'facts> {
description: PlanDescription<'a>,
facts: &'facts [NodeFacts],
footprint: PlanFootprint,
fact_owner: Option<RetainedRegion>,
}
impl<'a, 'facts> GraphPlan<'a, 'facts> {
pub fn validate(
description: PlanDescription<'a>,
facts: &'facts mut [NodeFacts],
footprint: PlanFootprint,
backing: PlanBacking<'_>,
context: &mut ValueContext<'_>,
) -> Result<Self, PlanError> {
validate::validate(description, facts, footprint, backing, context)?;
Ok(Self {
description,
facts,
footprint,
fact_owner: None,
})
}
pub fn validate_with_fact_vec(
description: PlanDescription<'a>,
facts: &'facts mut Vec<NodeFacts>,
footprint: PlanFootprint,
backing: PlanBacking<'_>,
context: &mut ValueContext<'_>,
) -> Result<Self, PlanError> {
let owner = RetainedRegion::vector(facts)?;
let mut result = Self::validate(description, facts, footprint, backing, context)?;
result.fact_owner = Some(owner);
Ok(result)
}
pub(crate) const fn fact_owner(&self) -> Option<RetainedRegion> {
self.fact_owner
}
pub(crate) fn verify_runtime_backing(
&self,
footprint: PlanFootprint,
backing: PlanBacking<'_>,
context: &mut ValueContext<'_>,
) -> Result<(), PlanError> {
if self.fact_owner.is_none() {
return Err(PlanError::Footprint);
}
validate::preflight(self.description, self.facts, footprint, backing, context)
}
pub fn validate_parameters(
&self,
bindings: &[ParameterBinding<'_>],
context: &mut ValueContext<'_>,
) -> Result<(), PlanError> {
parameters::validate(self.description.parameters, bindings, context)
}
pub const fn description(&self) -> PlanDescription<'a> {
self.description
}
pub fn facts(&self, id: PlanNodeId) -> Option<&NodeFacts> {
self.facts
.get(..self.description.operators.len())?
.get(id.0 as usize)
}
pub fn classification(&self) -> Classification {
let mut bits = self
.facts
.get(self.description.root.0 as usize)
.map_or(0, |fact| fact.classification.0);
for source in self.description.eager_searches {
bits |= self
.facts
.get(source.0 as usize)
.map_or(0, |fact| fact.classification.0);
}
Classification(bits)
}
pub const fn footprint(&self) -> PlanFootprint {
self.footprint
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PlanError {
Limit,
Footprint,
Reference,
Cycle,
Unreachable,
Arity,
Scope,
Type,
Parameter,
Barrier,
ReadAfterWrite,
ReadWriteSearch,
Aggregate,
Search,
PathBound,
Control(QueryError),
}
impl From<QueryError> for PlanError {
fn from(error: QueryError) -> Self {
Self::Control(error)
}
}
impl std::fmt::Display for PlanError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "graph plan validation: {self:?}")
}
}
impl std::error::Error for PlanError {}