use super::super::{CompletedGraphResult, UnsettledWriteResult};
use super::{
GraphBoundary, GraphQueryError, execute_native_boundary_result,
execute_native_mutation_diagnosed,
};
use crate::lifecycle::native_graph::{NativeMutationConsumer, NativeReadConsumer};
use crate::lifecycle::{QueryControl, Store};
use crate::property_graph::GraphName;
use crate::property_graph::query::expression::ExpressionCapacity;
use crate::property_graph::query::pattern::{
PatternCapacity, SearchAdapter, SearchHit, SearchInvocation,
};
use crate::property_graph::query::plan::{GraphPlan, ParameterBinding, PlanError};
use crate::property_graph::query::relational::StorageCapacity;
use crate::property_graph::query::resources::{QueryArena, QueryInputs, RetentionInventory};
use crate::property_graph::query::runtime::{
ArenaCapacity, ExecutionCapacity, NativeExecutionError, RuntimeContext, RuntimeLimits,
};
use crate::property_graph::staging::{GraphBatchReadView, StatementImages, WriteControl};
use crate::property_graph::storage::GraphReadView;
use crate::property_graph::storage::tree::directory::TreeError;
use std::cell::Cell;
#[derive(Clone, Copy)]
pub struct GraphQueryOptions {
pub(crate) slot_column_names: bool,
pub(crate) search_options: crate::property_graph::query::plan::SearchOptions,
pub(crate) limits: RuntimeLimits,
pub(crate) memory_limit: usize,
pub(crate) source_slots: usize,
pub(crate) pattern: PatternCapacity,
pub(crate) execution: ExecutionCapacity,
pub(crate) lazy_targets: usize,
pub(crate) overlay_capacity: usize,
pub(crate) image_capacity: usize,
}
impl GraphQueryOptions {
pub fn with_search_options(
mut self,
options: crate::property_graph::query::plan::SearchOptions,
) -> Result<Self, PlanError> {
if options.window.is_some()
|| options.hide_input
|| options
.alpha
.is_some_and(|alpha| !alpha.is_finite() || !(0.0..=1.0).contains(&alpha))
{
return Err(PlanError::Search);
}
self.search_options = options;
Ok(self)
}
pub const fn search_options(&self) -> crate::property_graph::query::plan::SearchOptions {
self.search_options
}
pub fn with_limits(
mut self,
memory_bytes: usize,
limits: RuntimeLimits,
) -> Result<Self, crate::property_graph::query::runtime::RuntimeError> {
if memory_bytes > 24 * 1024 * 1024 {
return Err(crate::property_graph::query::runtime::RuntimeError::BatchCapacity);
}
self.memory_limit = memory_bytes;
self.limits = limits;
Ok(self)
}
pub const fn memory_limit(&self) -> usize {
self.memory_limit
}
pub const fn runtime_limits(&self) -> RuntimeLimits {
self.limits
}
#[must_use]
pub fn with_slot_column_names(mut self) -> Self {
self.slot_column_names = true;
self
}
pub fn with_result_row_limit(
mut self,
rows: usize,
) -> Result<Self, crate::property_graph::query::runtime::RuntimeError> {
if !(1..=65_536).contains(&rows) {
return Err(crate::property_graph::query::runtime::RuntimeError::BatchCapacity);
}
self.execution.result_rows = rows;
self.execution.result.string_bytes = 64 * rows.max(1024);
self.execution.result_payload_bytes = self.execution.result.string_bytes.max(256 * 1024);
Ok(self)
}
}
impl Default for GraphQueryOptions {
fn default() -> Self {
let variable = ArenaCapacity {
string_bytes: 64 * 1024,
list_cells: 4096,
node_ids: 1024,
relationship_ids: 1024,
};
Self {
slot_column_names: false,
search_options: Default::default(),
limits: RuntimeLimits::default(),
memory_limit: 24 * 1024 * 1024,
source_slots: crate::property_graph::storage::MAX_NATIVE_ARTIFACTS,
pattern: PatternCapacity {
rows: StorageCapacity {
rows: 1024,
max_rows: 65_536,
payload_bytes: crate::property_graph::query::MAX_QUERY_BYTES,
variable: ArenaCapacity {
node_ids: crate::property_graph::query::MAX_LIST_ELEMENTS.min(
crate::property_graph::query::MAX_QUERY_BYTES
/ std::mem::size_of::<crate::property_graph::NodeId>(),
),
..variable
},
},
expression: ExpressionCapacity {
cells: 1024,
string_bytes: 64 * 1024,
},
},
execution: ExecutionCapacity {
batch_rows: 64,
result_rows: 1024,
batch_payload_bytes: 256 * 1024,
result_payload_bytes: 256 * 1024,
batch: variable,
result: variable,
},
lazy_targets: 1024,
overlay_capacity: 1024,
image_capacity: 1024,
}
}
}
pub struct GraphQuery<'q, 'plan, 'facts, 'a> {
pub plan: &'q GraphPlan<'plan, 'facts>,
pub inventory: RetentionInventory<'q, 'a>,
pub bindings: &'q [ParameterBinding<'q>],
pub columns: &'q [GraphName<'q>],
}
#[must_use]
pub struct Executed(());
pub(crate) enum NoSearch {}
impl<'v, 'm, 'g> SearchAdapter<'v, 'm, 'g> for NoSearch {
fn search<'s>(
&mut self,
_: &'s GraphReadView<'s, 'v, 'm, 'g>,
_: &SearchInvocation<'_, '_, 'v, 'm, 'g>,
_: &mut QueryArena<'m, 'g, SearchHit>,
_: &mut RuntimeContext<'v, 'm, 'g>,
) -> Result<crate::property_graph::query::completed::SearchReport, NativeExecutionError> {
match *self {}
}
}
#[allow(
clippy::large_enum_variant,
reason = "one per statement, on the stack; boxing the overlay would allocate per statement"
)]
enum Admission<'w, 'i, 'lease, 'm, 'g> {
Read {
view: &'w GraphReadView<'w, 'lease, 'm, 'g>,
search: Option<&'w mut dyn SearchAdapter<'lease, 'm, 'g>>,
},
Write {
view: &'w GraphReadView<'w, 'lease, 'm, 'g>,
overlay: GraphBatchReadView<'w, 'static>,
images: &'w StatementImages<'i>,
},
}
#[derive(Default)]
#[allow(
clippy::large_enum_variant,
reason = "one per statement, on the stack; the result moves out by value"
)]
enum Ran<'w> {
#[default]
Nothing,
NeedsWrite,
Read(CompletedGraphResult),
Write(UnsettledWriteResult, GraphBatchReadView<'w, 'static>),
}
pub struct GraphQueryExecutor<'x, 'w, 'i, 'lease, 'm, 'g> {
admission: Admission<'w, 'i, 'lease, 'm, 'g>,
options: &'x GraphQueryOptions,
ran: &'x Cell<Ran<'w>>,
boundary: Option<&'x dyn GraphBoundary>,
}
impl<'w, 'lease, 'm, 'g> GraphQueryExecutor<'_, 'w, '_, 'lease, 'm, 'g> {
pub fn run(
self,
runtime: &mut RuntimeContext<'lease, 'm, 'g>,
query: GraphQuery<'_, '_, '_, '_>,
) -> Result<Executed, GraphQueryError> {
let GraphQuery {
plan,
inventory,
bindings,
columns,
} = query;
let classification = plan.classification();
if let Some(boundary) = self.boundary {
boundary.classified(classification.writes())?;
}
let options = *self.options;
match self.admission {
Admission::Read { view, search } => {
if classification.writes() {
self.ran.set(Ran::NeedsWrite);
return Ok(Executed(()));
}
let admitted = QueryInputs::reserve(runtime.memory(), inventory, runtime.values())?
.admit_plan(plan, runtime.values())?;
if classification.searches() && search.is_none() {
return Err(GraphQueryError::from(NativeExecutionError::Plan(
PlanError::Search,
)));
}
let result = execute_native_boundary_result(
view,
runtime,
&admitted,
bindings,
columns,
options.pattern,
options.execution,
search,
self.boundary,
)?;
self.ran.set(Ran::Read(result));
}
Admission::Write {
view,
overlay,
images,
} => {
if !classification.writes() {
return Err(GraphQueryError::contract(
"the builder's plan wrote under the read admission but not under the writer",
));
}
let admitted = QueryInputs::reserve(runtime.memory(), inventory, runtime.values())?
.admit_plan(plan, runtime.values())?;
let (unsettled, overlay) = execute_native_mutation_diagnosed(
view,
runtime,
&admitted,
bindings,
columns,
options.pattern,
options.execution,
overlay,
images,
self.boundary,
)?;
self.ran.set(Ran::Write(unsettled, overlay));
}
}
Ok(Executed(()))
}
}
const NOT_EXECUTED: &str = "the builder returned without executing its plan";
struct ReadStatement<'b, S: ?Sized, B> {
options: &'b GraphQueryOptions,
search: Option<&'b mut S>,
build: &'b mut B,
boundary: Option<&'b dyn GraphBoundary>,
}
#[allow(
clippy::large_enum_variant,
reason = "returned once per statement; the result moves out by value"
)]
enum ReadRan {
Done(CompletedGraphResult),
NeedsWrite,
}
impl<S, B> NativeReadConsumer<Result<ReadRan, GraphQueryError>> for ReadStatement<'_, S, B>
where
S: for<'v, 'm, 'g> SearchAdapter<'v, 'm, 'g>,
B: for<'x, 'w, 'i, 'lease, 'm, 'g> FnMut(
&mut RuntimeContext<'lease, 'm, 'g>,
GraphQueryExecutor<'x, 'w, 'i, 'lease, 'm, 'g>,
) -> Result<Executed, GraphQueryError>,
{
fn consume<'s, 'lease, 'm, 'g>(
&mut self,
view: &GraphReadView<'s, 'lease, 'm, 'g>,
runtime: &mut RuntimeContext<'lease, 'm, 'g>,
) -> Result<Result<ReadRan, GraphQueryError>, TreeError> {
let ran = Cell::new(Ran::Nothing);
let search = self
.search
.as_deref_mut()
.map(|adapter| adapter as &mut dyn SearchAdapter<'lease, 'm, 'g>);
let executor = GraphQueryExecutor {
admission: Admission::Read { view, search },
options: self.options,
ran: &ran,
boundary: self.boundary,
};
let built = (self.build)(runtime, executor);
Ok(match (built, ran.take()) {
(Err(error), _) => Err(error),
(Ok(_), Ran::Read(result)) => Ok(ReadRan::Done(result)),
(Ok(_), Ran::NeedsWrite) => Ok(ReadRan::NeedsWrite),
(Ok(_), Ran::Nothing | Ran::Write(..)) => Err(GraphQueryError::contract(NOT_EXECUTED)),
})
}
}
struct WriteStatement<'b, B> {
options: &'b GraphQueryOptions,
build: &'b mut B,
boundary: Option<&'b dyn GraphBoundary>,
}
impl<B> NativeMutationConsumer<UnsettledWriteResult, GraphQueryError> for WriteStatement<'_, B>
where
B: for<'x, 'w, 'i, 'lease, 'm, 'g> FnMut(
&mut RuntimeContext<'lease, 'm, 'g>,
GraphQueryExecutor<'x, 'w, 'i, 'lease, 'm, 'g>,
) -> Result<Executed, GraphQueryError>,
{
fn consume<'lease, 'm, 'g, 'w, 'i>(
&mut self,
view: &'w GraphReadView<'w, 'lease, 'm, 'g>,
runtime: &mut RuntimeContext<'lease, 'm, 'g>,
overlay: GraphBatchReadView<'w, 'static>,
images: &'w StatementImages<'i>,
_: &mut WriteControl<'_>,
) -> Result<(UnsettledWriteResult, GraphBatchReadView<'w, 'static>), GraphQueryError> {
let ran = Cell::new(Ran::Nothing);
let executor = GraphQueryExecutor {
admission: Admission::Write {
view,
overlay,
images,
},
options: self.options,
ran: &ran,
boundary: self.boundary,
};
let Executed(()) = (self.build)(runtime, executor)?;
match ran.take() {
Ran::Write(result, overlay) => Ok((result, overlay)),
Ran::Nothing | Ran::NeedsWrite | Ran::Read(_) => {
Err(GraphQueryError::contract(NOT_EXECUTED))
}
}
}
}
impl Store {
pub(crate) fn execute_graph_query<S, B>(
&self,
control: &QueryControl,
options: &GraphQueryOptions,
search: Option<&mut S>,
build: B,
) -> Result<CompletedGraphResult, GraphQueryError>
where
S: for<'v, 'm, 'g> SearchAdapter<'v, 'm, 'g>,
B: for<'x, 'w, 'i, 'lease, 'm, 'g> FnMut(
&mut RuntimeContext<'lease, 'm, 'g>,
GraphQueryExecutor<'x, 'w, 'i, 'lease, 'm, 'g>,
) -> Result<Executed, GraphQueryError>,
{
self.execute_graph_query_with_boundary(control, options, search, build, None)
}
pub(crate) fn execute_graph_query_with_boundary<S, B>(
&self,
control: &QueryControl,
options: &GraphQueryOptions,
search: Option<&mut S>,
mut build: B,
boundary: Option<&dyn GraphBoundary>,
) -> Result<CompletedGraphResult, GraphQueryError>
where
S: for<'v, 'm, 'g> SearchAdapter<'v, 'm, 'g>,
B: for<'x, 'w, 'i, 'lease, 'm, 'g> FnMut(
&mut RuntimeContext<'lease, 'm, 'g>,
GraphQueryExecutor<'x, 'w, 'i, 'lease, 'm, 'g>,
) -> Result<Executed, GraphQueryError>,
{
let read = self.with_native_read(
control,
options.limits,
options.memory_limit,
options.source_slots,
ReadStatement {
options,
search,
build: &mut build,
boundary,
},
)?;
match read? {
ReadRan::Done(result) => return Ok(result),
ReadRan::NeedsWrite => {}
}
let (result, _) = self.with_native_mutation_settled(
control,
options.limits,
options.memory_limit,
options.source_slots,
options.lazy_targets,
options.overlay_capacity,
options.image_capacity,
WriteStatement {
options,
build: &mut build,
boundary,
},
|unsettled: UnsettledWriteResult, receipts, changed| {
unsettled.settle(receipts, changed)
},
)?;
Ok(result)
}
#[doc(hidden)]
pub fn execute_graph_statement<B>(
&self,
control: &QueryControl,
options: &GraphQueryOptions,
build: B,
) -> Result<CompletedGraphResult, GraphQueryError>
where
B: for<'x, 'w, 'i, 'lease, 'm, 'g> FnMut(
&mut RuntimeContext<'lease, 'm, 'g>,
GraphQueryExecutor<'x, 'w, 'i, 'lease, 'm, 'g>,
) -> Result<Executed, GraphQueryError>,
{
let mut adapter = super::search_adapter::NativeSearchAdapter::new(self);
self.execute_graph_query_with_boundary(control, options, Some(&mut adapter), build, None)
}
#[doc(hidden)]
pub fn execute_graph_statement_with_boundary<B>(
&self,
control: &QueryControl,
options: &GraphQueryOptions,
build: B,
boundary: Option<&dyn GraphBoundary>,
) -> Result<CompletedGraphResult, GraphQueryError>
where
B: for<'x, 'w, 'i, 'lease, 'm, 'g> FnMut(
&mut RuntimeContext<'lease, 'm, 'g>,
GraphQueryExecutor<'x, 'w, 'i, 'lease, 'm, 'g>,
) -> Result<Executed, GraphQueryError>,
{
let mut adapter = super::search_adapter::NativeSearchAdapter::new(self);
self.execute_graph_query_with_boundary(
control,
options,
Some(&mut adapter),
build,
boundary,
)
}
#[cfg(feature = "test-seams")]
#[doc(hidden)]
pub fn create_graph_store(
path: impl AsRef<std::path::Path>,
options: crate::lifecycle::OpenOptions,
) -> Result<Self, GraphQueryError> {
Ok(Self::create_native_graph(path, options, None)?)
}
#[cfg(feature = "test-seams")]
#[doc(hidden)]
pub fn create_graph_store_with_test_dependencies(
path: impl AsRef<std::path::Path>,
options: crate::lifecycle::OpenOptions,
dependencies: crate::lifecycle::StoreTestDependencies,
) -> Result<Self, GraphQueryError> {
Ok(Self::create_native_graph_with_infrastructure(
path,
options,
None,
dependencies.vfs,
dependencies.clock,
&mut crate::property_graph::storage::allocation::OsEntropy,
)?)
}
#[cfg(feature = "test-seams")]
#[doc(hidden)]
pub fn open_graph_store_with_test_dependencies(
path: impl AsRef<std::path::Path>,
options: crate::lifecycle::OpenOptions,
dependencies: crate::lifecycle::StoreTestDependencies,
) -> Result<Self, GraphQueryError> {
Ok(Self::open_native_graph_with_infrastructure(
path,
options,
None,
dependencies.vfs,
dependencies.clock,
)?)
}
#[cfg(feature = "test-seams")]
#[doc(hidden)]
pub fn open_graph_store(
path: impl AsRef<std::path::Path>,
options: crate::lifecycle::OpenOptions,
) -> Result<Self, GraphQueryError> {
Ok(Self::open_native_graph(path, options, None)?)
}
}