use crate::error::EngineError;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::hash::{BuildHasherDefault, Hasher};
#[cfg(test)]
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceSpan {
pub offset: usize,
pub length: usize,
pub line: u32,
pub column: u32,
}
impl SourceSpan {
pub const fn new(offset: usize, length: usize, line: u32, column: u32) -> Self {
Self {
offset,
length,
line,
column,
}
}
pub fn end_offset(&self) -> usize {
self.offset.saturating_add(self.length)
}
}
pub type GqlParams = BTreeMap<String, GqlParamValue>;
#[derive(Clone, Debug, PartialEq)]
pub enum GqlParamValue {
Null,
Bool(bool),
Int(i64),
UInt(u64),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<GqlParamValue>),
Map(BTreeMap<String, GqlParamValue>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GqlStatementKind {
Query,
Mutation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GqlExecutionMode {
Auto,
ReadOnly,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GqlExecutionOptions {
pub mode: GqlExecutionMode,
pub allow_full_scan: bool,
pub max_rows: usize,
pub cursor: Option<String>,
pub max_cursor_bytes: usize,
pub max_mutation_rows: usize,
pub max_mutation_ops: usize,
pub max_pipeline_rows: usize,
pub max_groups: usize,
pub max_collect_items: usize,
pub max_union_branches: usize,
pub max_subquery_invocations: usize,
pub max_subquery_depth: usize,
pub max_shortest_path_pairs: usize,
pub max_query_bytes: usize,
pub max_param_bytes: usize,
pub max_ast_depth: usize,
pub max_literal_items: usize,
pub max_intermediate_bindings: usize,
pub max_frontier: usize,
pub max_path_hops: u8,
pub max_paths_per_start: usize,
pub max_order_materialization: usize,
pub max_skip: usize,
pub include_plan: bool,
pub profile: bool,
pub compact_rows: bool,
pub include_vectors: bool,
}
impl Default for GqlExecutionOptions {
fn default() -> Self {
Self {
mode: GqlExecutionMode::Auto,
allow_full_scan: false,
max_rows: 10_000,
cursor: None,
max_cursor_bytes: 16 * 1024,
max_mutation_rows: 10_000,
max_mutation_ops: 50_000,
max_pipeline_rows: 65_536,
max_groups: 65_536,
max_collect_items: 65_536,
max_union_branches: 16,
max_subquery_invocations: 4_096,
max_subquery_depth: 2,
max_shortest_path_pairs: 4_096,
max_query_bytes: 1_048_576,
max_param_bytes: 1_048_576,
max_ast_depth: 256,
max_literal_items: 10_000,
max_intermediate_bindings: 65_536,
max_frontier: 65_536,
max_path_hops: 16,
max_paths_per_start: 4_096,
max_order_materialization: 65_536,
max_skip: 100_000,
include_plan: false,
profile: false,
compact_rows: false,
include_vectors: false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum GqlValue {
Null,
Bool(bool),
Int(i64),
UInt(u64),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<GqlValue>),
Map(BTreeMap<String, GqlValue>),
Node(GqlNode),
Edge(GqlEdge),
Path(GqlPath),
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlNode {
pub id: Option<u64>,
pub labels: Option<Vec<String>>,
pub key: Option<String>,
pub props: Option<BTreeMap<String, GqlValue>>,
pub weight: Option<f32>,
pub created_at: Option<i64>,
pub updated_at: Option<i64>,
pub dense_vector: Option<Vec<f32>>,
pub sparse_vector: Option<Vec<(u32, f32)>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlEdge {
pub id: Option<u64>,
pub from: Option<u64>,
pub to: Option<u64>,
pub label: Option<String>,
pub props: Option<BTreeMap<String, GqlValue>>,
pub weight: Option<f32>,
pub created_at: Option<i64>,
pub updated_at: Option<i64>,
pub valid_from: Option<i64>,
pub valid_to: Option<i64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlPath {
pub node_ids: Vec<u64>,
pub edge_ids: Vec<u64>,
pub nodes: Option<Vec<GqlNode>>,
pub edges: Option<Vec<GqlEdge>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlRow {
pub values: Vec<GqlValue>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlExecutionResult {
pub kind: GqlStatementKind,
pub columns: Vec<String>,
pub rows: Vec<GqlRow>,
pub next_cursor: Option<String>,
pub stats: GqlExecutionStats,
pub mutation_stats: Option<GqlMutationStats>,
pub plan: Option<GqlExecutionExplain>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlExecutionStats {
pub rows_returned: usize,
pub rows_matched: usize,
pub rows_after_filter: usize,
pub intermediate_bindings: usize,
pub db_hits: usize,
pub elapsed_us: Option<u64>,
pub warnings: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlMutationStats {
pub rows_matched: usize,
pub mutation_rows: usize,
pub mutation_ops: usize,
pub nodes_created: usize,
pub nodes_updated: usize,
pub nodes_deleted: usize,
pub edges_created: usize,
pub edges_updated: usize,
pub edges_deleted: usize,
pub labels_added: usize,
pub labels_removed: usize,
pub properties_set: usize,
pub properties_removed: usize,
pub skipped_null_targets: usize,
pub duplicate_targets: usize,
pub db_hits: usize,
pub elapsed_us: Option<u64>,
pub warnings: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlExplain {
pub columns: Vec<String>,
pub target: GqlLoweringTarget,
pub native_plan: Option<QueryPlan>,
pub pushed_down: Vec<String>,
pub residual: Vec<String>,
pub projection: Vec<String>,
pub row_ops: Vec<GqlRowOperation>,
pub caps: GqlCapSummary,
pub warnings: Vec<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GqlLoweringTarget {
NodeQuery,
EdgeQuery,
GraphRowQuery,
GraphPipelineQuery,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GqlRowOperation {
ResidualFilter,
Projection,
Sort,
Skip,
Limit,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GqlCapSummary {
pub allow_full_scan: bool,
pub max_rows: usize,
pub max_intermediate_bindings: usize,
pub max_skip: usize,
pub max_query_bytes: usize,
pub max_param_bytes: usize,
pub max_ast_depth: usize,
pub max_literal_items: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlExecutionExplain {
pub kind: GqlStatementKind,
pub columns: Vec<String>,
pub read: Option<GqlExplain>,
pub mutation: Option<GqlMutationExplain>,
pub caps: GqlExecutionCapSummary,
pub warnings: Vec<String>,
pub notes: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlMutationExplain {
pub read_prefix: Option<GqlMutationReadPrefixExplain>,
pub operations: Vec<GqlMutationOperationExplain>,
pub return_plan: Option<GqlMutationReturnExplain>,
pub would_create_node_labels: Vec<String>,
pub would_create_edge_labels: Vec<String>,
pub uses_transaction_snapshot: bool,
pub uses_write_txn: bool,
pub replacement_adapters: bool,
pub atomic_commit: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GqlMutationReadPrefixExplain {
pub graph_row_target: GqlExplain,
pub internal_columns: Vec<String>,
pub target_aliases: Vec<String>,
pub expression_columns: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GqlMutationOperationExplain {
pub op: String,
pub target_alias: Option<String>,
pub row_multiplicity: String,
pub detail: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GqlMutationReturnExplain {
pub columns: Vec<String>,
pub order_items: usize,
pub skip: usize,
pub limit: Option<usize>,
pub post_commit_hydration: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GqlExecutionCapSummary {
pub allow_full_scan: bool,
pub max_rows: usize,
pub max_cursor_bytes: usize,
pub max_mutation_rows: usize,
pub max_mutation_ops: usize,
pub max_pipeline_rows: usize,
pub max_groups: usize,
pub max_collect_items: usize,
pub max_union_branches: usize,
pub max_subquery_invocations: usize,
pub max_subquery_depth: usize,
pub max_shortest_path_pairs: usize,
pub max_query_bytes: usize,
pub max_param_bytes: usize,
pub max_ast_depth: usize,
pub max_literal_items: usize,
pub max_intermediate_bindings: usize,
pub max_frontier: usize,
pub max_path_hops: u8,
pub max_paths_per_start: usize,
pub max_order_materialization: usize,
pub max_skip: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GqlSemanticErrorCode {
DuplicateAlias,
UnknownVariable,
InvalidParameter,
ParameterTypeMismatch,
InvalidReturnExpression,
InvalidPropertyAccess,
DynamicLabelNotSupported,
DynamicRelationshipTypeNotSupported,
FullScanNotAllowed,
ReadOnlyViolation,
}
pub(crate) const LABEL_TOKEN_SCHEMA_VERSION: u32 = 1;
#[allow(dead_code)]
pub(crate) const MAX_NODE_LABELS_PER_NODE: usize = 10;
pub(crate) fn validate_label_token_name(name: &str) -> Result<(), EngineError> {
if name.is_empty() {
return Err(EngineError::InvalidOperation(
"label token name must not be empty".to_string(),
));
}
if name.len() > 255 {
return Err(EngineError::InvalidOperation(format!(
"label token name must be at most 255 UTF-8 bytes, got {}",
name.len()
)));
}
if name.trim_matches(char::is_whitespace).len() != name.len() {
return Err(EngineError::InvalidOperation(
"label token name must not contain leading or trailing whitespace".to_string(),
));
}
if name
.chars()
.any(|ch| ch == '\0' || (ch.is_ascii() && ch.is_control()))
{
return Err(EngineError::InvalidOperation(
"label token name must not contain ASCII control characters or NUL".to_string(),
));
}
Ok(())
}
#[allow(dead_code)]
pub(crate) fn validate_public_node_label_list<'a, I>(labels: I) -> Result<(), EngineError>
where
I: IntoIterator<Item = &'a str>,
{
ValidatedNodeLabelList::new(labels).map(|_| ())
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub(crate) struct ValidatedNodeLabelList<'a> {
count: u8,
labels: [&'a str; MAX_NODE_LABELS_PER_NODE],
}
#[allow(dead_code)]
impl<'a> ValidatedNodeLabelList<'a> {
pub(crate) fn new<I>(labels: I) -> Result<Self, EngineError>
where
I: IntoIterator<Item = &'a str>,
{
let mut stored: [&'a str; MAX_NODE_LABELS_PER_NODE] = [""; MAX_NODE_LABELS_PER_NODE];
let mut count = 0usize;
let mut total_count = 0usize;
for label in labels {
validate_label_token_name(label)?;
total_count += 1;
if count < MAX_NODE_LABELS_PER_NODE {
stored[count] = label;
count += 1;
}
}
if total_count == 0 {
return Err(EngineError::InvalidOperation(
"node label set must contain at least one label".to_string(),
));
}
if total_count > MAX_NODE_LABELS_PER_NODE {
return Err(EngineError::InvalidOperation(format!(
"node label set must contain at most {} labels",
MAX_NODE_LABELS_PER_NODE
)));
}
for idx in 0..count {
if stored[..idx].iter().any(|&seen| seen == stored[idx]) {
return Err(EngineError::InvalidOperation(format!(
"node label set contains duplicate label '{}'",
stored[idx]
)));
}
}
Ok(Self {
count: count as u8,
labels: stored,
})
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.count as usize
}
#[inline]
pub(crate) fn as_slice(&self) -> &[&'a str] {
&self.labels[..self.len()]
}
}
#[allow(dead_code)]
pub(crate) fn validate_node_label_filter(filter: &NodeLabelFilter) -> Result<(), EngineError> {
validate_public_node_label_list(filter.labels.iter().map(String::as_str))
}
mod node_label_input_seal {
pub trait Sealed {}
}
pub trait IntoNodeLabels: node_label_input_seal::Sealed {
fn into_node_labels(self) -> Vec<String>;
}
impl node_label_input_seal::Sealed for &str {}
impl IntoNodeLabels for &str {
fn into_node_labels(self) -> Vec<String> {
vec![self.to_string()]
}
}
impl node_label_input_seal::Sealed for String {}
impl IntoNodeLabels for String {
fn into_node_labels(self) -> Vec<String> {
vec![self]
}
}
impl node_label_input_seal::Sealed for &String {}
impl IntoNodeLabels for &String {
fn into_node_labels(self) -> Vec<String> {
vec![self.clone()]
}
}
impl node_label_input_seal::Sealed for &[&str] {}
impl IntoNodeLabels for &[&str] {
fn into_node_labels(self) -> Vec<String> {
self.iter().map(|label| (*label).to_string()).collect()
}
}
impl node_label_input_seal::Sealed for &[String] {}
impl IntoNodeLabels for &[String] {
fn into_node_labels(self) -> Vec<String> {
self.to_vec()
}
}
impl node_label_input_seal::Sealed for Vec<String> {}
impl IntoNodeLabels for Vec<String> {
fn into_node_labels(self) -> Vec<String> {
self
}
}
impl<const N: usize> node_label_input_seal::Sealed for &[&str; N] {}
impl<const N: usize> IntoNodeLabels for &[&str; N] {
fn into_node_labels(self) -> Vec<String> {
self.as_slice().into_node_labels()
}
}
impl<const N: usize> node_label_input_seal::Sealed for &[String; N] {}
impl<const N: usize> IntoNodeLabels for &[String; N] {
fn into_node_labels(self) -> Vec<String> {
self.as_slice().into_node_labels()
}
}
#[doc(hidden)]
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) struct NodeLabelSet {
count: u8,
label_ids: [u32; MAX_NODE_LABELS_PER_NODE],
}
#[allow(dead_code)]
impl NodeLabelSet {
pub(crate) fn single(label_id: u32) -> Result<Self, EngineError> {
Self::from_canonical_ids(&[label_id])
}
pub(crate) fn from_label_ids<I>(label_ids: I) -> Result<Self, EngineError>
where
I: IntoIterator<Item = u32>,
{
let mut ids = [0u32; MAX_NODE_LABELS_PER_NODE];
let mut count = 0usize;
for label_id in label_ids {
if count == MAX_NODE_LABELS_PER_NODE {
return Err(EngineError::InvalidOperation(format!(
"node label set must contain at most {} labels",
MAX_NODE_LABELS_PER_NODE
)));
}
ids[count] = label_id;
count += 1;
}
if count == 0 {
return Err(EngineError::InvalidOperation(
"node label set must contain at least one label".to_string(),
));
}
ids[..count].sort_unstable();
Self::from_sorted_prefix(ids, count)
}
pub(crate) fn from_canonical_ids(label_ids: &[u32]) -> Result<Self, EngineError> {
if label_ids.is_empty() {
return Err(EngineError::InvalidOperation(
"node label set must contain at least one label".to_string(),
));
}
if label_ids.len() > MAX_NODE_LABELS_PER_NODE {
return Err(EngineError::InvalidOperation(format!(
"node label set must contain at most {} labels",
MAX_NODE_LABELS_PER_NODE
)));
}
let mut ids = [0u32; MAX_NODE_LABELS_PER_NODE];
for (idx, &label_id) in label_ids.iter().enumerate() {
ids[idx] = label_id;
if label_id == 0 {
return Err(EngineError::InvalidOperation(
"node label token ID 0 is reserved".to_string(),
));
}
if idx > 0 && label_ids[idx - 1] >= label_id {
return Err(EngineError::InvalidOperation(
"node label IDs must be sorted ascending and unique".to_string(),
));
}
}
Ok(Self {
count: label_ids.len() as u8,
label_ids: ids,
})
}
fn from_sorted_prefix(
label_ids: [u32; MAX_NODE_LABELS_PER_NODE],
count: usize,
) -> Result<Self, EngineError> {
for idx in 0..count {
if label_ids[idx] == 0 {
return Err(EngineError::InvalidOperation(
"node label token ID 0 is reserved".to_string(),
));
}
if idx > 0 && label_ids[idx - 1] == label_ids[idx] {
return Err(EngineError::InvalidOperation(format!(
"node label set contains duplicate label ID {}",
label_ids[idx]
)));
}
}
Ok(Self {
count: count as u8,
label_ids,
})
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.count as usize
}
#[inline]
pub(crate) fn as_slice(&self) -> &[u32] {
&self.label_ids[..self.len()]
}
#[inline]
pub(crate) fn contains(&self, label_id: u32) -> bool {
self.as_slice().binary_search(&label_id).is_ok()
}
#[inline]
pub(crate) fn contains_all(&self, required: &NodeLabelSet) -> bool {
required
.as_slice()
.iter()
.all(|&label_id| self.contains(label_id))
}
#[inline]
pub(crate) fn contains_any(&self, candidates: &NodeLabelSet) -> bool {
candidates
.as_slice()
.iter()
.any(|&label_id| self.contains(label_id))
}
#[inline]
pub(crate) fn single_label_id(&self) -> u32 {
debug_assert_eq!(self.len(), 1);
self.as_slice()[0]
}
pub(crate) fn require_single_label_id(&self, context: &str) -> Result<u32, EngineError> {
if self.len() == 1 {
Ok(self.single_label_id())
} else {
Err(EngineError::InvalidOperation(format!(
"{context} currently supports exactly one node label, got {}",
self.len()
)))
}
}
}
impl fmt::Debug for NodeLabelSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("NodeLabelSet")
.field(&self.as_slice())
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LabelMatchMode {
Any,
All,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NodeLabelFilter {
pub labels: Vec<String>,
pub mode: LabelMatchMode,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ResolvedNodeLabelFilter {
Unconstrained,
Empty {
mode: LabelMatchMode,
unknown_label_count: usize,
},
LabelSet {
mode: LabelMatchMode,
label_ids: NodeLabelSet,
unknown_label_count: usize,
},
}
#[allow(dead_code)]
impl ResolvedNodeLabelFilter {
pub(crate) fn known(
mode: LabelMatchMode,
label_ids: NodeLabelSet,
unknown_label_count: usize,
) -> Self {
Self::LabelSet {
mode,
label_ids,
unknown_label_count,
}
}
pub(crate) fn empty(mode: LabelMatchMode, unknown_label_count: usize) -> Self {
Self::Empty {
mode,
unknown_label_count,
}
}
#[inline]
pub(crate) fn mode(&self) -> Option<LabelMatchMode> {
match self {
Self::Unconstrained => None,
Self::Empty { mode, .. } | Self::LabelSet { mode, .. } => Some(*mode),
}
}
#[inline]
pub(crate) fn label_ids(&self) -> Option<NodeLabelSet> {
match self {
Self::LabelSet { label_ids, .. } => Some(*label_ids),
Self::Unconstrained | Self::Empty { .. } => None,
}
}
#[inline]
pub(crate) fn is_empty_constraint(&self) -> bool {
matches!(self, Self::Empty { .. })
}
}
#[doc(hidden)]
#[derive(Default)]
pub struct NodeIdHasher(u64);
impl Hasher for NodeIdHasher {
#[inline]
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
let mut value = 0u64;
for (index, byte) in bytes.iter().take(8).enumerate() {
value |= (*byte as u64) << (index * 8);
}
self.0 = value;
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0 = i;
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.0 = i as u64;
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0 = i as u64;
}
}
#[doc(hidden)]
pub type NodeIdBuildHasher = BuildHasherDefault<NodeIdHasher>;
pub type NodeIdMap<V> = HashMap<u64, V, NodeIdBuildHasher>;
pub type NodeIdSet = HashSet<u64, NodeIdBuildHasher>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct NodeVisibilityMeta {
pub(crate) label_ids: NodeLabelSet,
pub(crate) updated_at: i64,
pub(crate) weight: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum NodeVisibilityState {
Live(NodeVisibilityMeta),
Deleted,
Missing,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct NodeMetadataForQuery {
pub(crate) id: u64,
pub(crate) label_ids: NodeLabelSet,
pub(crate) updated_at: i64,
pub(crate) weight: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SelectedNodeFields {
pub(crate) meta: NodeMetadataForQuery,
pub(crate) key: Option<String>,
pub(crate) props: BTreeMap<String, PropValue>,
pub(crate) created_at: Option<i64>,
pub(crate) dense_vector: Option<DenseVector>,
pub(crate) sparse_vector: Option<SparseVector>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EdgeVisibilityState {
Live,
Deleted,
Missing,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct EdgeMetadataForQuery {
pub(crate) id: u64,
pub(crate) from: u64,
pub(crate) to: u64,
pub(crate) label_id: u32,
pub(crate) updated_at: i64,
pub(crate) weight: f32,
pub(crate) valid_from: i64,
pub(crate) valid_to: i64,
}
impl From<&EdgeRecord> for EdgeMetadataForQuery {
fn from(edge: &EdgeRecord) -> Self {
Self {
id: edge.id,
from: edge.from,
to: edge.to,
label_id: edge.label_id,
updated_at: edge.updated_at,
weight: edge.weight,
valid_from: edge.valid_from,
valid_to: edge.valid_to,
}
}
}
impl From<crate::edge_metadata::EdgeMetadataCandidate> for EdgeMetadataForQuery {
fn from(meta: crate::edge_metadata::EdgeMetadataCandidate) -> Self {
Self {
id: meta.edge_id,
from: meta.from,
to: meta.to,
label_id: meta.label_id,
updated_at: meta.updated_at,
weight: meta.weight,
valid_from: meta.valid_from,
valid_to: meta.valid_to,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct SelectedEdgeFields {
pub(crate) meta: EdgeMetadataForQuery,
pub(crate) props: BTreeMap<String, PropValue>,
pub(crate) created_at: Option<i64>,
}
#[cfg(test)]
#[derive(Default)]
pub(crate) struct SelectedFieldReadCounters {
node_selected_field_batches: AtomicUsize,
node_selected_field_ids: AtomicUsize,
edge_selected_field_batches: AtomicUsize,
edge_selected_field_ids: AtomicUsize,
node_dense_vector_projection_reads: AtomicUsize,
node_sparse_vector_projection_reads: AtomicUsize,
}
#[cfg(test)]
impl SelectedFieldReadCounters {
pub(crate) fn note_node_selected_field_batch(&self, ids: usize) {
self.node_selected_field_batches
.fetch_add(1, Ordering::Relaxed);
self.node_selected_field_ids
.fetch_add(ids, Ordering::Relaxed);
}
pub(crate) fn note_edge_selected_field_batch(&self, ids: usize) {
self.edge_selected_field_batches
.fetch_add(1, Ordering::Relaxed);
self.edge_selected_field_ids
.fetch_add(ids, Ordering::Relaxed);
}
pub(crate) fn note_node_dense_vector_projection_read(&self) {
self.node_dense_vector_projection_reads
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn note_node_sparse_vector_projection_read(&self) {
self.node_sparse_vector_projection_reads
.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn node_selected_field_batches(&self) -> usize {
self.node_selected_field_batches.load(Ordering::Relaxed)
}
pub(crate) fn node_selected_field_ids(&self) -> usize {
self.node_selected_field_ids.load(Ordering::Relaxed)
}
pub(crate) fn edge_selected_field_batches(&self) -> usize {
self.edge_selected_field_batches.load(Ordering::Relaxed)
}
pub(crate) fn edge_selected_field_ids(&self) -> usize {
self.edge_selected_field_ids.load(Ordering::Relaxed)
}
pub(crate) fn node_dense_vector_projection_reads(&self) -> usize {
self.node_dense_vector_projection_reads
.load(Ordering::Relaxed)
}
pub(crate) fn node_sparse_vector_projection_reads(&self) -> usize {
self.node_sparse_vector_projection_reads
.load(Ordering::Relaxed)
}
pub(crate) fn reset(&self) {
self.node_selected_field_batches.store(0, Ordering::Relaxed);
self.node_selected_field_ids.store(0, Ordering::Relaxed);
self.edge_selected_field_batches.store(0, Ordering::Relaxed);
self.edge_selected_field_ids.store(0, Ordering::Relaxed);
self.node_dense_vector_projection_reads
.store(0, Ordering::Relaxed);
self.node_sparse_vector_projection_reads
.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PropValue {
Null,
Bool(bool),
Int(i64),
UInt(u64),
Float(f64),
String(String),
Bytes(Vec<u8>),
Array(Vec<PropValue>),
Map(BTreeMap<String, PropValue>),
}
pub(crate) fn fnv1a(bytes: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &b in bytes {
hash ^= b as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
pub fn hash_prop_key(key: &str) -> u64 {
fnv1a(key.as_bytes())
}
pub fn hash_prop_value(value: &PropValue) -> u64 {
let bytes = rmp_serde::to_vec(value).expect("PropValue must be serializable");
fnv1a(&bytes)
}
pub type DenseVector = Vec<f32>;
pub type SparseVector = Vec<(u32, f32)>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DenseMetric {
Cosine,
Euclidean,
DotProduct,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HnswConfig {
pub m: u16,
pub ef_construction: u16,
}
pub const DEFAULT_DENSE_EF_SEARCH: usize = 128;
impl Default for HnswConfig {
fn default() -> Self {
Self {
m: 16,
ef_construction: 200,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DenseVectorConfig {
pub dimension: u32,
pub metric: DenseMetric,
#[serde(default)]
pub hnsw: HnswConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VectorSearchMode {
Dense,
Sparse,
Hybrid,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum FusionMode {
#[default]
WeightedRankFusion,
ReciprocalRankFusion,
WeightedScoreFusion,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VectorSearchScope {
pub start_node_id: u64,
pub max_depth: u32,
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub at_epoch: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VectorSearchRequest {
pub mode: VectorSearchMode,
pub dense_query: Option<DenseVector>,
pub sparse_query: Option<SparseVector>,
pub k: usize,
pub label_filter: Option<NodeLabelFilter>,
pub ef_search: Option<usize>,
pub scope: Option<VectorSearchScope>,
pub dense_weight: Option<f32>,
pub sparse_weight: Option<f32>,
pub fusion_mode: Option<FusionMode>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VectorHit {
pub node_id: u64,
pub score: f32,
}
pub fn validate_dense_vector_config(config: &DenseVectorConfig) -> Result<(), EngineError> {
if config.dimension == 0 {
return Err(EngineError::InvalidOperation(
"dense vector dimension must be > 0".into(),
));
}
if config.hnsw.m == 0 {
return Err(EngineError::InvalidOperation(
"dense HNSW m must be > 0".into(),
));
}
if config.hnsw.ef_construction == 0 {
return Err(EngineError::InvalidOperation(
"dense HNSW ef_construction must be > 0".into(),
));
}
if config.hnsw.ef_construction < config.hnsw.m {
return Err(EngineError::InvalidOperation(format!(
"dense HNSW ef_construction ({}) must be >= m ({})",
config.hnsw.ef_construction, config.hnsw.m
)));
}
Ok(())
}
fn validate_finite_vector_component(value: f32, context: &str) -> Result<(), EngineError> {
if !value.is_finite() {
return Err(EngineError::InvalidOperation(format!(
"{} contains NaN or infinite value",
context
)));
}
Ok(())
}
pub fn validate_dense_vector(
values: &[f32],
config: &DenseVectorConfig,
) -> Result<(), EngineError> {
validate_dense_vector_config(config)?;
if values.len() != config.dimension as usize {
return Err(EngineError::InvalidOperation(format!(
"dense vector length {} does not match configured dimension {}",
values.len(),
config.dimension
)));
}
for &value in values {
validate_finite_vector_component(value, "dense vector")?;
}
Ok(())
}
fn canonicalize_sparse_vector_entries(
mut entries: SparseVector,
) -> Result<Option<SparseVector>, EngineError> {
if entries.is_empty() {
return Ok(None);
}
for &(_, weight) in &entries {
validate_finite_vector_component(weight, "sparse vector")?;
if weight < 0.0 {
return Err(EngineError::InvalidOperation(
"sparse vector weights must be non-negative".into(),
));
}
}
entries.sort_unstable_by_key(|&(dimension_id, _)| dimension_id);
let mut canonical = Vec::with_capacity(entries.len());
for (dimension_id, weight) in entries {
if let Some((last_dimension_id, last_weight)) = canonical.last_mut() {
if *last_dimension_id == dimension_id {
*last_weight += weight;
continue;
}
}
canonical.push((dimension_id, weight));
}
canonical.retain(|&(_, weight)| weight != 0.0);
if canonical.is_empty() {
return Ok(None);
}
for &(_, weight) in &canonical {
validate_finite_vector_component(weight, "sparse vector")?;
}
Ok(Some(canonical))
}
pub fn canonicalize_sparse_vector(
values: &[(u32, f32)],
) -> Result<Option<SparseVector>, EngineError> {
canonicalize_sparse_vector_entries(values.to_vec())
}
pub fn canonicalize_sparse_vector_owned(
values: SparseVector,
) -> Result<Option<SparseVector>, EngineError> {
canonicalize_sparse_vector_entries(values)
}
#[derive(Debug, Clone, Copy)]
pub struct TombstoneEntry {
pub deleted_at: i64,
pub last_write_seq: u64,
}
#[doc(hidden)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct NodeRecord {
pub id: u64,
pub label_ids: NodeLabelSet,
pub key: String,
pub props: BTreeMap<String, PropValue>,
pub created_at: i64,
pub updated_at: i64,
pub weight: f32,
#[serde(default)]
pub dense_vector: Option<DenseVector>,
#[serde(default)]
pub sparse_vector: Option<SparseVector>,
#[serde(default)]
pub last_write_seq: u64,
}
#[doc(hidden)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct EdgeRecord {
pub id: u64,
pub from: u64,
pub to: u64,
pub label_id: u32,
pub props: BTreeMap<String, PropValue>,
pub created_at: i64,
pub updated_at: i64,
pub weight: f32,
pub valid_from: i64,
pub valid_to: i64,
#[serde(default)]
pub last_write_seq: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NodeView {
pub id: u64,
pub labels: Vec<String>,
pub key: String,
pub props: BTreeMap<String, PropValue>,
pub created_at: i64,
pub updated_at: i64,
pub weight: f32,
pub dense_vector: Option<DenseVector>,
pub sparse_vector: Option<SparseVector>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EdgeView {
pub id: u64,
pub from: u64,
pub to: u64,
pub label: String,
pub props: BTreeMap<String, PropValue>,
pub created_at: i64,
pub updated_at: i64,
pub weight: f32,
pub valid_from: i64,
pub valid_to: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NodeKeyQuery {
pub label: String,
pub key: String,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PageRequest {
pub limit: Option<usize>,
pub after: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct PageResult<T> {
pub items: Vec<T>,
pub next_cursor: Option<u64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodeQuery {
pub label_filter: Option<NodeLabelFilter>,
pub ids: Vec<u64>,
pub keys: Vec<String>,
pub filter: Option<NodeFilterExpr>,
pub page: PageRequest,
pub order: NodeQueryOrder,
pub allow_full_scan: bool,
}
impl Default for NodeQuery {
fn default() -> Self {
Self {
label_filter: None,
ids: Vec::new(),
keys: Vec::new(),
filter: None,
page: PageRequest::default(),
order: NodeQueryOrder::NodeIdAsc,
allow_full_scan: false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeFilterExpr {
PropertyEquals {
key: String,
value: PropValue,
},
PropertyIn {
key: String,
values: Vec<PropValue>,
},
PropertyRange {
key: String,
lower: Option<PropertyRangeBound>,
upper: Option<PropertyRangeBound>,
},
PropertyExists {
key: String,
},
PropertyMissing {
key: String,
},
UpdatedAtRange {
lower_ms: Option<i64>,
upper_ms: Option<i64>,
},
And(Vec<NodeFilterExpr>),
Or(Vec<NodeFilterExpr>),
Not(Box<NodeFilterExpr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeQueryOrder {
NodeIdAsc,
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryNodeIdsResult {
pub items: Vec<u64>,
pub next_cursor: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct QueryNodesResult {
pub items: Vec<NodeView>,
pub next_cursor: Option<u64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EdgeQuery {
pub label: Option<String>,
pub ids: Vec<u64>,
pub from_ids: Vec<u64>,
pub to_ids: Vec<u64>,
pub endpoint_ids: Vec<u64>,
pub filter: Option<EdgeFilterExpr>,
pub page: PageRequest,
pub order: EdgeQueryOrder,
pub allow_full_scan: bool,
}
impl Default for EdgeQuery {
fn default() -> Self {
Self {
label: None,
ids: Vec::new(),
from_ids: Vec::new(),
to_ids: Vec::new(),
endpoint_ids: Vec::new(),
filter: None,
page: PageRequest::default(),
order: EdgeQueryOrder::EdgeIdAsc,
allow_full_scan: false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum EdgeFilterExpr {
PropertyEquals {
key: String,
value: PropValue,
},
PropertyIn {
key: String,
values: Vec<PropValue>,
},
PropertyRange {
key: String,
lower: Option<PropertyRangeBound>,
upper: Option<PropertyRangeBound>,
},
PropertyExists {
key: String,
},
PropertyMissing {
key: String,
},
WeightRange {
lower: Option<f32>,
upper: Option<f32>,
},
UpdatedAtRange {
lower_ms: Option<i64>,
upper_ms: Option<i64>,
},
ValidAt {
epoch_ms: i64,
},
ValidFromRange {
lower_ms: Option<i64>,
upper_ms: Option<i64>,
},
ValidToRange {
lower_ms: Option<i64>,
upper_ms: Option<i64>,
},
And(Vec<EdgeFilterExpr>),
Or(Vec<EdgeFilterExpr>),
Not(Box<EdgeFilterExpr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeQueryOrder {
EdgeIdAsc,
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryEdgeIdsResult {
pub edge_ids: Vec<u64>,
pub next_cursor: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct QueryEdgesResult {
pub edges: Vec<EdgeView>,
pub next_cursor: Option<u64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphRowQuery {
pub nodes: Vec<GraphNodePattern>,
pub pieces: Vec<GraphPatternPiece>,
pub where_: Option<GraphExpr>,
pub return_items: Option<Vec<GraphReturnItem>>,
pub order_by: Vec<GraphOrderItem>,
pub page: GraphPageRequest,
pub at_epoch: Option<i64>,
pub params: BTreeMap<String, GraphParamValue>,
pub output: GraphOutputOptions,
pub options: GraphQueryOptions,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphPipelineQuery {
pub stages: Vec<GraphPipelineStage>,
pub params: BTreeMap<String, GraphParamValue>,
pub at_epoch: Option<i64>,
pub page: GraphPageRequest,
pub output: GraphOutputOptions,
pub options: GraphPipelineOptions,
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphPipelineStage {
Match(GraphPipelineMatchStage),
Project(GraphProjectStage),
ShortestPath(GraphShortestPathStage),
Call(GraphSubqueryStage),
Union(GraphUnionStage),
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphPipelineMatchStage {
pub optional: bool,
pub nodes: Vec<GraphNodePattern>,
pub pieces: Vec<GraphPatternPiece>,
pub where_: Option<GraphExpr>,
pub optional_candidate_where: Option<GraphExpr>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphProjectStage {
pub kind: GraphProjectKind,
pub items: GraphProjectionItems,
pub distinct: bool,
pub where_: Option<GraphExpr>,
pub order_by: Vec<GraphOrderItem>,
pub skip: Option<GraphExpr>,
pub limit: Option<GraphExpr>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphProjectKind {
With,
Return,
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphProjectionItems {
Star,
Items(Vec<GraphProjectItem>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphProjectItem {
pub expr: GraphExpr,
pub alias: Option<String>,
pub projection: GraphReturnProjection,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphUnionStage {
pub branches: Vec<GraphPipelineQuery>,
pub all: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphSubqueryStage {
pub query: Box<GraphPipelineQuery>,
pub import_aliases: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphShortestPathStage {
pub optional: bool,
pub output_path_alias: String,
pub mode: GraphShortestPathMode,
pub from: GraphShortestPathEndpoint,
pub to: GraphShortestPathEndpoint,
pub direction: Direction,
pub edge_label_filter: Vec<String>,
pub min_hops: u8,
pub max_hops: u8,
pub weight_field: Option<String>,
pub max_cost: Option<f64>,
pub max_paths: Option<usize>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphShortestPathMode {
One,
All,
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphShortestPathEndpoint {
Alias(String),
NodeId(u64),
NodeKey { label: String, key: String },
Expr(GraphExpr),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphPipelineOptions {
pub allow_full_scan: bool,
pub max_rows: usize,
pub max_pipeline_rows: usize,
pub max_groups: usize,
pub max_collect_items: usize,
pub max_union_branches: usize,
pub max_subquery_invocations: usize,
pub max_subquery_depth: usize,
pub max_shortest_path_pairs: usize,
pub max_intermediate_bindings: usize,
pub max_frontier: usize,
pub max_path_hops: u8,
pub max_paths_per_start: usize,
pub max_order_materialization: usize,
pub max_skip: usize,
pub max_cursor_bytes: usize,
pub max_query_bytes: usize,
pub max_param_bytes: usize,
pub max_ast_depth: usize,
pub max_literal_items: usize,
pub include_plan: bool,
pub profile: bool,
}
impl Default for GraphPipelineOptions {
fn default() -> Self {
Self {
allow_full_scan: false,
max_rows: 10_000,
max_pipeline_rows: 65_536,
max_groups: 65_536,
max_collect_items: 65_536,
max_union_branches: 16,
max_subquery_invocations: 4_096,
max_subquery_depth: 2,
max_shortest_path_pairs: 4_096,
max_intermediate_bindings: 65_536,
max_frontier: 65_536,
max_path_hops: 16,
max_paths_per_start: 4_096,
max_order_materialization: 65_536,
max_skip: 100_000,
max_cursor_bytes: 16 * 1024,
max_query_bytes: 1_048_576,
max_param_bytes: 1_048_576,
max_ast_depth: 256,
max_literal_items: 10_000,
include_plan: false,
profile: false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphParamValue {
Null,
Bool(bool),
Int(i64),
UInt(u64),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<GraphParamValue>),
Map(BTreeMap<String, GraphParamValue>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphNodePattern {
pub alias: String,
pub label_filter: Option<NodeLabelFilter>,
pub ids: Vec<u64>,
pub keys: Vec<NodeKeyQuery>,
pub filter: Option<NodeFilterExpr>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphPatternPiece {
Edge(GraphEdgePattern),
Optional(GraphOptionalGroup),
VariableLength(GraphVariableLengthPattern),
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphEdgePattern {
pub alias: Option<String>,
pub from_alias: String,
pub to_alias: String,
pub direction: Direction,
pub label_filter: Vec<String>,
pub filter: Option<EdgeFilterExpr>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphOptionalGroup {
pub pieces: Vec<GraphPatternPiece>,
pub where_: Option<GraphExpr>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphVariableLengthPattern {
pub path_alias: Option<String>,
pub edge_alias: Option<String>,
pub from_alias: String,
pub to_alias: String,
pub direction: Direction,
pub label_filter: Vec<String>,
pub filter: Option<EdgeFilterExpr>,
pub min_hops: u8,
pub max_hops: u8,
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphExpr {
Null,
Bool(bool),
Int(i64),
UInt(u64),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<GraphExpr>),
Map(BTreeMap<String, GraphExpr>),
Param(String),
Binding(String),
Property {
alias: String,
key: String,
},
NodeField {
alias: String,
field: GraphNodeField,
},
EdgeField {
alias: String,
field: GraphEdgeField,
},
PathField {
alias: String,
field: GraphPathField,
},
Function {
name: GraphFunction,
args: Vec<GraphExpr>,
},
AggregateCall {
function: GraphAggregateFunction,
distinct: bool,
arg: Option<Box<GraphExpr>>,
},
ExistsSubquery(GraphSubqueryStage),
Unary {
op: GraphUnaryOp,
expr: Box<GraphExpr>,
},
Binary {
left: Box<GraphExpr>,
op: GraphBinaryOp,
right: Box<GraphExpr>,
},
Case {
operand: Option<Box<GraphExpr>>,
branches: Vec<GraphCaseBranch>,
else_expr: Option<Box<GraphExpr>>,
},
IsNull(Box<GraphExpr>),
IsNotNull(Box<GraphExpr>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphCaseBranch {
pub when: GraphExpr,
pub then: GraphExpr,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GraphNodeField {
Id,
Labels,
Key,
Weight,
CreatedAt,
UpdatedAt,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GraphEdgeField {
Id,
From,
To,
Label,
Weight,
CreatedAt,
UpdatedAt,
ValidFrom,
ValidTo,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GraphPathField {
NodeIds,
EdgeIds,
Length,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GraphFunction {
Id,
Labels,
Type,
Length,
StartNode,
EndNode,
Nodes,
Relationships,
Coalesce,
ToString,
ToInteger,
ToFloat,
Abs,
Floor,
Ceil,
Round,
Lower,
Upper,
Trim,
Substring,
Size,
Head,
Last,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GraphAggregateFunction {
Count,
Sum,
Avg,
Min,
Max,
Collect,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GraphUnaryOp {
Not,
Neg,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GraphBinaryOp {
Or,
And,
Eq,
Neq,
Lt,
Le,
Gt,
Ge,
In,
Add,
Sub,
Mul,
Div,
StartsWith,
EndsWith,
Contains,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphReturnItem {
pub expr: GraphExpr,
pub alias: Option<String>,
pub projection: GraphReturnProjection,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GraphReturnProjection {
Auto,
IdOnly,
Element(GraphElementProjection),
Selected(GraphSelectedProjection),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GraphElementProjection {
IdOnly,
Compact,
Full,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GraphSelectedProjection {
Node(GraphSelectedNodeProjection),
Edge(GraphSelectedEdgeProjection),
Path(GraphSelectedPathProjection),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphSelectedNodeProjection {
pub id: bool,
pub labels: bool,
pub key: bool,
pub props: GraphPropertySelection,
pub weight: bool,
pub created_at: bool,
pub updated_at: bool,
pub vectors: GraphVectorSelection,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphSelectedEdgeProjection {
pub id: bool,
pub from: bool,
pub to: bool,
pub label: bool,
pub props: GraphPropertySelection,
pub weight: bool,
pub created_at: bool,
pub updated_at: bool,
pub valid_from: bool,
pub valid_to: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphSelectedPathProjection {
pub node_ids: bool,
pub edge_ids: bool,
pub nodes: Option<GraphSelectedNodeProjection>,
pub edges: Option<GraphSelectedEdgeProjection>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GraphPropertySelection {
None,
Keys(Vec<String>),
All,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphVectorSelection {
None,
Dense,
Sparse,
Both,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphOutputOptions {
pub mode: GraphOutputMode,
pub compact_rows: bool,
pub include_vectors: bool,
}
impl Default for GraphOutputOptions {
fn default() -> Self {
Self {
mode: GraphOutputMode::Ids,
compact_rows: false,
include_vectors: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GraphOutputMode {
Ids,
Elements,
Projected,
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphValue {
Null,
Bool(bool),
Int(i64),
UInt(u64),
Float(f64),
String(String),
Bytes(Vec<u8>),
List(Vec<GraphValue>),
Map(BTreeMap<String, GraphValue>),
NodeId(u64),
EdgeId(u64),
Node(GraphNodeValue),
Edge(GraphEdgeValue),
Path(GraphPathValue),
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphNodeValue {
pub id: Option<u64>,
pub labels: Option<Vec<String>>,
pub key: Option<String>,
pub props: Option<BTreeMap<String, GraphValue>>,
pub weight: Option<f32>,
pub created_at: Option<i64>,
pub updated_at: Option<i64>,
pub dense_vector: Option<Vec<f32>>,
pub sparse_vector: Option<Vec<(u32, f32)>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphEdgeValue {
pub id: Option<u64>,
pub from: Option<u64>,
pub to: Option<u64>,
pub label: Option<String>,
pub props: Option<BTreeMap<String, GraphValue>>,
pub weight: Option<f32>,
pub created_at: Option<i64>,
pub updated_at: Option<i64>,
pub valid_from: Option<i64>,
pub valid_to: Option<i64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphPath {
pub nodes: Vec<u64>,
pub edges: Vec<u64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphPathValue {
pub node_ids: Vec<u64>,
pub edge_ids: Vec<u64>,
pub nodes: Option<Vec<GraphNodeValue>>,
pub edges: Option<Vec<GraphEdgeValue>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphOrderItem {
pub expr: GraphExpr,
pub direction: GraphOrderDirection,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphOrderDirection {
Asc,
Desc,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphPageRequest {
pub skip: usize,
pub limit: usize,
pub cursor: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphQueryOptions {
pub allow_full_scan: bool,
pub max_intermediate_bindings: usize,
pub max_frontier: usize,
pub max_path_hops: u8,
pub max_paths_per_start: usize,
pub max_page_limit: usize,
pub max_order_materialization: usize,
pub max_cursor_bytes: usize,
pub max_query_bytes: usize,
pub include_plan: bool,
pub profile: bool,
}
impl Default for GraphQueryOptions {
fn default() -> Self {
Self {
allow_full_scan: false,
max_intermediate_bindings: 65_536,
max_frontier: 65_536,
max_path_hops: 16,
max_paths_per_start: 4_096,
max_page_limit: 10_000,
max_order_materialization: 65_536,
max_cursor_bytes: 16 * 1024,
max_query_bytes: 1_048_576,
include_plan: false,
profile: false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphRowResult {
pub columns: Vec<String>,
pub rows: Vec<GraphRow>,
pub next_cursor: Option<String>,
pub stats: GraphRowStats,
pub plan: Option<GraphRowExplain>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphRow {
pub values: Vec<GraphValue>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphRowStats {
pub rows_returned: usize,
pub rows_after_filter: usize,
pub rows_seen_for_page: usize,
pub intermediate_bindings_peak: usize,
pub frontier_peak: usize,
pub paths_enumerated: usize,
pub db_hits: usize,
pub elapsed_us: Option<u64>,
pub effective_at_epoch: i64,
pub warnings: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphPipelineResult {
pub columns: Vec<String>,
pub rows: Vec<GraphRow>,
pub next_cursor: Option<String>,
pub stats: GraphPipelineStats,
pub plan: Option<GraphPipelineExplain>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphPipelineStats {
pub rows_returned: usize,
pub rows_entered_pipeline: usize,
pub rows_after_filter: usize,
pub intermediate_rows: usize,
pub pipeline_rows_materialized: usize,
pub groups: usize,
pub collect_items: usize,
pub union_branches: usize,
pub union_dedup_keys: usize,
pub subquery_invocations: usize,
pub subquery_cache_hits: usize,
pub shortest_path_pairs: usize,
pub shortest_path_cache_hits: usize,
pub db_hits: usize,
pub elapsed_us: Option<u64>,
pub effective_at_epoch: i64,
pub warnings: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphRowExplain {
pub columns: Vec<String>,
pub effective_at_epoch: Option<i64>,
pub fingerprint: String,
pub plan: Vec<GraphExplainNode>,
pub row_ops: Vec<GraphRowOperationExplain>,
pub order: GraphOrderExplain,
pub cursor: GraphCursorExplain,
pub projection: GraphProjectionExplain,
pub caps: GraphCapExplain,
pub summaries: GraphExecutionSummaries,
pub warnings: Vec<String>,
pub notes: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphPipelineExplain {
pub columns: Vec<String>,
pub effective_at_epoch: Option<i64>,
pub fingerprint: String,
pub stages: Vec<GraphPipelineStageExplain>,
pub row_ops: Vec<GraphRowOperationExplain>,
pub order: GraphOrderExplain,
pub cursor: GraphCursorExplain,
pub projection: GraphProjectionExplain,
pub caps: GraphPipelineCapExplain,
pub summaries: GraphExecutionSummaries,
pub stats: GraphPipelineStats,
pub warnings: Vec<String>,
pub notes: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GraphPipelineStageExplain {
pub index: usize,
pub kind: String,
pub detail: String,
pub columns: Vec<String>,
pub graph_row: Option<Box<GraphRowExplain>>,
pub warnings: Vec<String>,
pub notes: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphPipelineCapExplain {
pub allow_full_scan: bool,
pub max_rows: usize,
pub max_pipeline_rows: usize,
pub max_groups: usize,
pub max_collect_items: usize,
pub max_union_branches: usize,
pub max_subquery_invocations: usize,
pub max_subquery_depth: usize,
pub max_shortest_path_pairs: usize,
pub max_intermediate_bindings: usize,
pub max_frontier: usize,
pub max_path_hops: u8,
pub max_paths_per_start: usize,
pub max_order_materialization: usize,
pub max_skip: usize,
pub max_cursor_bytes: usize,
pub max_query_bytes: usize,
pub max_param_bytes: usize,
pub max_ast_depth: usize,
pub max_literal_items: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphExplainNode {
pub kind: String,
pub detail: String,
pub children: Vec<GraphExplainNode>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphRowOperationExplain {
pub kind: String,
pub detail: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphOrderExplain {
pub explicit: bool,
pub items: usize,
pub stable_logical_row_key: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphCursorExplain {
pub supplied: bool,
pub codec_implemented: bool,
pub message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphProjectionExplain {
pub columns: Vec<String>,
pub output_mode: GraphOutputMode,
pub include_vectors: bool,
pub compact_rows: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphCapExplain {
pub allow_full_scan: bool,
pub max_intermediate_bindings: usize,
pub max_frontier: usize,
pub max_path_hops: u8,
pub max_paths_per_start: usize,
pub max_page_limit: usize,
pub max_order_materialization: usize,
pub max_cursor_bytes: usize,
pub max_query_bytes: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GraphExecutionSummaries {
pub validation_only: bool,
pub rows_planned: usize,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryPlanKind {
NodeQuery,
EdgeQuery,
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryPlan {
pub kind: QueryPlanKind,
pub root: QueryPlanNode,
pub estimated_candidates: Option<u64>,
pub warnings: Vec<QueryPlanWarning>,
pub notes: Vec<QueryPlanNote>,
pub public_inputs: QueryPlanPublicInputs,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct QueryPlanPublicInputs {
pub node_labels: Vec<QueryPlanPublicName>,
pub edge_labels: Vec<QueryPlanPublicName>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryPlanPublicName {
pub alias: Option<String>,
pub name: String,
pub known: bool,
pub mode: Option<LabelMatchMode>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueryPlanNote {
NodeLabelAnyDedupeBeforePagination,
NodeLabelAnyFinalVerification,
NodeLabelAllSupersetVerification,
StaleNodeLabelMembershipVerification,
}
#[derive(Debug, Clone, PartialEq)]
pub enum QueryPlanNode {
ExplicitIds,
KeyLookup,
NodeLabelIndex,
NodeLabelAnyIndex,
PropertyEqualityIndex,
PropertyRangeIndex,
TimestampIndex,
AdjacencyExpansion,
ExplicitEdgeIds,
EdgeLabelIndex,
EdgeTripleIndex,
EdgeEndpointAdjacency,
EdgeWeightIndex,
EdgeUpdatedAtIndex,
EdgeValidityIndex,
EdgeMetadataScan,
EdgePropertyEqualityIndex,
EdgePropertyRangeIndex,
Intersect { inputs: Vec<QueryPlanNode> },
Union { inputs: Vec<QueryPlanNode> },
VerifyNodeFilter { input: Box<QueryPlanNode> },
VerifyEdgeFilter { input: Box<QueryPlanNode> },
VerifyEdgePredicates { input: Box<QueryPlanNode> },
FallbackNodeLabelScan,
FallbackFullNodeScan,
FallbackEdgeLabelScan,
FallbackFullEdgeScan,
EmptyResult,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueryPlanWarning {
MissingReadyIndex,
UsingFallbackScan,
FullScanRequiresOptIn,
FullScanExplicitlyAllowed,
EdgePropertyPostFilter,
IndexSkippedAsBroad,
CandidateCapExceeded,
RangeCandidateCapExceeded,
TimestampCandidateCapExceeded,
VerifyOnlyFilter,
BooleanBranchFallback,
PlanningProbeBudgetExceeded,
UnknownNodeLabel,
UnknownEdgeLabel,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SecondaryIndexKind {
Equality,
Range,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SecondaryIndexTarget {
NodeProperty { label_id: u32, prop_key: String },
EdgeProperty { label_id: u32, prop_key: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SecondaryIndexState {
Building,
Ready,
Failed,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecondaryIndexManifestEntry {
pub index_id: u64,
pub target: SecondaryIndexTarget,
pub kind: SecondaryIndexKind,
pub state: SecondaryIndexState,
#[serde(default)]
pub last_error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodePropertyIndexInfo {
pub index_id: u64,
pub label: String,
pub prop_key: String,
pub kind: SecondaryIndexKind,
pub state: SecondaryIndexState,
pub last_error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NodeLabelInfo {
pub label: String,
pub label_id: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EdgeLabelInfo {
pub label: String,
pub label_id: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EdgePropertyIndexInfo {
pub index_id: u64,
pub label: String,
pub prop_key: String,
pub kind: SecondaryIndexKind,
pub state: SecondaryIndexState,
pub last_error: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyRangeBound {
Included(PropValue),
Excluded(PropValue),
}
impl PropertyRangeBound {
pub fn value(&self) -> &PropValue {
match self {
PropertyRangeBound::Included(value) | PropertyRangeBound::Excluded(value) => value,
}
}
pub fn is_inclusive(&self) -> bool {
matches!(self, PropertyRangeBound::Included(_))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PropertyRangeCursor {
pub value: PropValue,
pub node_id: u64,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PropertyRangePageRequest {
pub limit: Option<usize>,
pub after: Option<PropertyRangeCursor>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropertyRangePageResult<T> {
pub items: Vec<T>,
pub next_cursor: Option<PropertyRangeCursor>,
}
#[derive(Debug, Clone)]
pub(crate) enum WalOp {
UpsertNode(NodeRecord),
UpsertEdge(EdgeRecord),
DeleteNode { id: u64, deleted_at: i64 },
DeleteEdge { id: u64, deleted_at: i64 },
EnsureNodeLabel { label: String, label_id: u32 },
EnsureEdgeLabel { label: String, label_id: u32 },
BeginAtomicBatch { first_seq: u64, op_count: u32 },
CommitAtomicBatch { first_seq: u64, op_count: u32 },
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum OpTag {
UpsertNode = 1,
UpsertEdge = 2,
DeleteNode = 3,
DeleteEdge = 4,
EnsureNodeLabel = 5,
EnsureEdgeLabel = 6,
BeginAtomicBatch = 7,
CommitAtomicBatch = 8,
}
impl OpTag {
pub(crate) fn from_u8(v: u8) -> Option<OpTag> {
match v {
1 => Some(OpTag::UpsertNode),
2 => Some(OpTag::UpsertEdge),
3 => Some(OpTag::DeleteNode),
4 => Some(OpTag::DeleteEdge),
5 => Some(OpTag::EnsureNodeLabel),
6 => Some(OpTag::EnsureEdgeLabel),
7 => Some(OpTag::BeginAtomicBatch),
8 => Some(OpTag::CommitAtomicBatch),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SegmentInfo {
pub id: u64,
pub node_count: u64,
pub edge_count: u64,
#[serde(default)]
pub segment_format_version: u32,
#[serde(default)]
pub segment_data_id: [u8; 32],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestState {
pub version: u32,
#[serde(default)]
pub label_token_schema_version: u32,
#[serde(default)]
pub node_label_tokens: BTreeMap<String, u32>,
#[serde(default)]
pub edge_label_tokens: BTreeMap<String, u32>,
#[serde(default)]
pub next_node_label_id: u32,
#[serde(default)]
pub next_edge_label_id: u32,
pub segments: Vec<SegmentInfo>,
pub next_node_id: u64,
pub next_edge_id: u64,
#[serde(default)]
pub dense_vector: Option<DenseVectorConfig>,
#[serde(default)]
pub prune_policies: BTreeMap<String, PrunePolicy>,
#[serde(default)]
pub next_engine_seq: u64,
#[serde(default)]
pub next_wal_generation_id: u64,
#[serde(default)]
pub active_wal_generation_id: u64,
#[serde(default)]
pub pending_flush_epochs: Vec<FlushEpochMeta>,
#[serde(default)]
pub secondary_indexes: Vec<SecondaryIndexManifestEntry>,
#[serde(default)]
pub next_secondary_index_id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FlushEpochState {
FrozenPendingFlush,
PublishedPendingRetire,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlushEpochMeta {
pub epoch_id: u64,
pub wal_generation_id: u64,
pub state: FlushEpochState,
pub segment_id: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompactionPhase {
CollectingTombstones,
MergingNodes,
MergingEdges,
WritingOutput,
}
#[derive(Debug, Clone)]
pub struct CompactionProgress {
pub phase: CompactionPhase,
pub segments_processed: usize,
pub total_segments: usize,
pub records_processed: u64,
pub total_records: u64,
}
#[derive(Debug, Clone)]
pub struct CompactionStats {
pub segments_merged: usize,
pub nodes_kept: u64,
pub nodes_removed: u64,
pub edges_kept: u64,
pub edges_removed: u64,
pub duration_ms: u64,
pub output_segment_id: u64,
pub nodes_auto_pruned: u64,
pub edges_auto_pruned: u64,
}
#[derive(Debug, Clone)]
pub struct NodeInput {
pub labels: Vec<String>,
pub key: String,
pub props: BTreeMap<String, PropValue>,
pub weight: f32,
pub dense_vector: Option<DenseVector>,
pub sparse_vector: Option<SparseVector>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpsertNodeOptions {
pub props: BTreeMap<String, PropValue>,
pub weight: f32,
pub dense_vector: Option<DenseVector>,
pub sparse_vector: Option<SparseVector>,
}
impl Default for UpsertNodeOptions {
fn default() -> Self {
Self {
props: BTreeMap::new(),
weight: 1.0,
dense_vector: None,
sparse_vector: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpsertEdgeOptions {
pub props: BTreeMap<String, PropValue>,
pub weight: f32,
pub valid_from: Option<i64>,
pub valid_to: Option<i64>,
}
impl Default for UpsertEdgeOptions {
fn default() -> Self {
Self {
props: BTreeMap::new(),
weight: 1.0,
valid_from: None,
valid_to: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TxnLocalRef {
Slot(u32),
Alias(String),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TxnNodeRef {
Id(u64),
Key { label: String, key: String },
Local(TxnLocalRef),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TxnEdgeRef {
Id(u64),
Triple {
from: TxnNodeRef,
to: TxnNodeRef,
label: String,
},
Local(TxnLocalRef),
}
#[derive(Debug, Clone, PartialEq)]
pub enum TxnIntent {
UpsertNode {
alias: Option<String>,
labels: Vec<String>,
key: String,
options: UpsertNodeOptions,
},
UpsertEdge {
alias: Option<String>,
from: TxnNodeRef,
to: TxnNodeRef,
label: String,
options: UpsertEdgeOptions,
},
DeleteNode {
target: TxnNodeRef,
},
DeleteEdge {
target: TxnEdgeRef,
},
InvalidateEdge {
target: TxnEdgeRef,
valid_to: i64,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct TxnNodeView {
pub id: Option<u64>,
pub local: Option<TxnLocalRef>,
pub labels: Vec<String>,
pub key: String,
pub props: BTreeMap<String, PropValue>,
pub created_at: Option<i64>,
pub updated_at: Option<i64>,
pub weight: f32,
pub dense_vector: Option<DenseVector>,
pub sparse_vector: Option<SparseVector>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TxnEdgeView {
pub id: Option<u64>,
pub local: Option<TxnLocalRef>,
pub from: TxnNodeRef,
pub to: TxnNodeRef,
pub label: String,
pub props: BTreeMap<String, PropValue>,
pub created_at: Option<i64>,
pub updated_at: Option<i64>,
pub weight: f32,
pub valid_from: Option<i64>,
pub valid_to: Option<i64>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TxnCommitResult {
pub node_ids: Vec<u64>,
pub edge_ids: Vec<u64>,
pub local_node_ids: BTreeMap<TxnLocalRef, u64>,
pub local_edge_ids: BTreeMap<TxnLocalRef, u64>,
}
impl TxnCommitResult {
pub fn node_id(&self, target: &TxnNodeRef) -> Option<u64> {
match target {
TxnNodeRef::Id(id) => Some(*id),
TxnNodeRef::Local(local) => self.local_node_ids.get(local).copied(),
TxnNodeRef::Key { .. } => None,
}
}
pub fn edge_id(&self, target: &TxnEdgeRef) -> Option<u64> {
match target {
TxnEdgeRef::Id(id) => Some(*id),
TxnEdgeRef::Local(local) => self.local_edge_ids.get(local).copied(),
TxnEdgeRef::Triple { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NeighborOptions {
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub limit: Option<usize>,
pub at_epoch: Option<i64>,
pub decay_lambda: Option<f32>,
}
impl Default for NeighborOptions {
fn default() -> Self {
Self {
direction: Direction::Outgoing,
edge_label_filter: None,
limit: None,
at_epoch: None,
decay_lambda: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DegreeOptions {
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub at_epoch: Option<i64>,
}
impl Default for DegreeOptions {
fn default() -> Self {
Self {
direction: Direction::Outgoing,
edge_label_filter: None,
at_epoch: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TopKOptions {
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub scoring: ScoringMode,
pub at_epoch: Option<i64>,
}
impl Default for TopKOptions {
fn default() -> Self {
Self {
direction: Direction::Outgoing,
edge_label_filter: None,
scoring: ScoringMode::Weight,
at_epoch: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TraverseOptions {
pub min_depth: u32,
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub emit_node_label_filter: Option<NodeLabelFilter>,
pub at_epoch: Option<i64>,
pub decay_lambda: Option<f64>,
pub limit: Option<usize>,
pub cursor: Option<TraversalCursor>,
}
impl Default for TraverseOptions {
fn default() -> Self {
Self {
min_depth: 1,
direction: Direction::Outgoing,
edge_label_filter: None,
emit_node_label_filter: None,
at_epoch: None,
decay_lambda: None,
limit: None,
cursor: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SubgraphOptions {
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub node_label_filter: Option<NodeLabelFilter>,
pub at_epoch: Option<i64>,
}
impl Default for SubgraphOptions {
fn default() -> Self {
Self {
direction: Direction::Outgoing,
edge_label_filter: None,
node_label_filter: None,
at_epoch: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShortestPathOptions {
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub weight_field: Option<String>,
pub at_epoch: Option<i64>,
pub max_depth: Option<u32>,
pub max_cost: Option<f64>,
}
impl Default for ShortestPathOptions {
fn default() -> Self {
Self {
direction: Direction::Outgoing,
edge_label_filter: None,
weight_field: None,
at_epoch: None,
max_depth: None,
max_cost: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AllShortestPathsOptions {
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub weight_field: Option<String>,
pub at_epoch: Option<i64>,
pub max_depth: Option<u32>,
pub max_cost: Option<f64>,
pub max_paths: Option<usize>,
}
impl Default for AllShortestPathsOptions {
fn default() -> Self {
Self {
direction: Direction::Outgoing,
edge_label_filter: None,
weight_field: None,
at_epoch: None,
max_depth: None,
max_cost: None,
max_paths: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IsConnectedOptions {
pub direction: Direction,
pub edge_label_filter: Option<Vec<String>>,
pub at_epoch: Option<i64>,
pub max_depth: Option<u32>,
}
impl Default for IsConnectedOptions {
fn default() -> Self {
Self {
direction: Direction::Outgoing,
edge_label_filter: None,
at_epoch: None,
max_depth: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ComponentOptions {
pub edge_label_filter: Option<Vec<String>>,
pub node_label_filter: Option<NodeLabelFilter>,
pub at_epoch: Option<i64>,
}
#[derive(Debug, Clone)]
pub struct EdgeInput {
pub from: u64,
pub to: u64,
pub label: String,
pub props: BTreeMap<String, PropValue>,
pub weight: f32,
pub valid_from: Option<i64>,
pub valid_to: Option<i64>,
}
#[derive(Debug, Clone, Default)]
pub struct GraphPatch {
pub upsert_nodes: Vec<NodeInput>,
pub upsert_edges: Vec<EdgeInput>,
pub invalidate_edges: Vec<(u64, i64)>,
pub delete_node_ids: Vec<u64>,
pub delete_edge_ids: Vec<u64>,
}
#[derive(Debug, Clone)]
pub struct PatchResult {
pub node_ids: Vec<u64>,
pub edge_ids: Vec<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PrunePolicyInfo {
pub name: String,
pub policy: PrunePolicy,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PrunePolicy {
pub max_age_ms: Option<i64>,
pub max_weight: Option<f32>,
pub label: Option<String>,
}
#[derive(Debug, Clone)]
pub struct PruneResult {
pub nodes_pruned: u64,
pub edges_pruned: u64,
}
#[derive(Debug, Clone)]
pub struct DbStats {
pub pending_wal_bytes: usize,
pub segment_count: usize,
pub node_tombstone_count: usize,
pub edge_tombstone_count: usize,
pub last_compaction_ms: Option<i64>,
pub wal_sync_mode: String,
pub active_memtable_bytes: usize,
pub immutable_memtable_bytes: usize,
pub immutable_memtable_count: usize,
pub pending_flush_count: usize,
pub active_wal_generation_id: u64,
pub oldest_retained_wal_generation_id: u64,
}
#[derive(Debug, Clone)]
pub struct ScrubReport {
pub segments: Vec<SegmentScrubResult>,
pub total_components_checked: u64,
pub total_components_ok: u64,
pub total_components_failed: u64,
pub total_bytes_digested: u64,
pub duration_ms: u64,
}
#[derive(Debug, Clone)]
pub struct SegmentScrubResult {
pub segment_id: u64,
pub findings: Vec<ComponentScrubFinding>,
pub components_ok: u64,
pub bytes_digested: u64,
}
#[derive(Debug, Clone)]
pub struct ComponentScrubFinding {
pub component_kind: String,
pub finding_type: ScrubFindingType,
pub detail: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrubFindingType {
PayloadDigestMismatch,
ComponentIdMismatch,
DependencyDigestMismatch,
IdentityHeaderMismatch,
ContainerIdMismatch,
SegmentIdentityMismatch,
SemanticMismatch,
RangeOverflow,
RangeOverlap,
FileMissing,
IoError,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Direction {
Outgoing,
Incoming,
Both,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NeighborEntry {
pub node_id: u64,
pub edge_id: u64,
pub label: String,
pub weight: f32,
pub valid_from: i64,
pub valid_to: i64,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct NeighborRecord {
pub node_id: u64,
pub edge_id: u64,
pub edge_label_id: u32,
pub weight: f32,
pub valid_from: i64,
pub valid_to: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TraversalHit {
pub node_id: u64,
pub depth: u32,
pub via_edge_id: Option<u64>,
pub score: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TraversalCursor {
pub depth: u32,
pub last_node_id: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TraversalPageResult {
pub items: Vec<TraversalHit>,
pub next_cursor: Option<TraversalCursor>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ScoringMode {
Weight,
Recency,
DecayAdjusted { lambda: f32 },
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShortestPath {
pub nodes: Vec<u64>,
pub edges: Vec<u64>,
pub total_cost: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Subgraph {
pub nodes: Vec<NodeView>,
pub edges: Vec<EdgeView>,
}
#[derive(Debug, Clone)]
pub struct PprOptions {
pub algorithm: PprAlgorithm,
pub damping_factor: f64,
pub max_iterations: u32,
pub epsilon: f64,
pub approx_residual_tolerance: f64,
pub edge_label_filter: Option<Vec<String>>,
pub max_results: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PprAlgorithm {
ExactPowerIteration,
ApproxForwardPush,
}
impl Default for PprOptions {
fn default() -> Self {
PprOptions {
algorithm: PprAlgorithm::ExactPowerIteration,
damping_factor: 0.85,
max_iterations: 20,
epsilon: 1e-6,
approx_residual_tolerance: 1e-5,
edge_label_filter: None,
max_results: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PprApproxMeta {
pub residual_tolerance: f64,
pub pushes: u64,
pub max_remaining_residual: f64,
}
#[derive(Debug, Clone)]
pub struct PprResult {
pub scores: Vec<(u64, f64)>,
pub iterations: u32,
pub converged: bool,
pub algorithm: PprAlgorithm,
pub approx: Option<PprApproxMeta>,
}
#[derive(Debug, Clone)]
pub struct ExportOptions {
pub node_label_filter: Option<NodeLabelFilter>,
pub edge_label_filter: Option<Vec<String>>,
pub include_weights: bool,
}
impl Default for ExportOptions {
fn default() -> Self {
Self {
node_label_filter: None,
edge_label_filter: None,
include_weights: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExportEdge {
pub from: u64,
pub to: u64,
pub edge_label_index: u32,
pub weight: Option<f32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AdjacencyExport {
pub node_ids: Vec<u64>,
#[serde(default)]
pub node_labels: Vec<String>,
#[serde(default)]
pub node_label_indexes: Vec<Vec<u32>>,
pub edge_labels: Vec<String>,
pub edges: Vec<ExportEdge>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WalSyncMode {
Immediate,
GroupCommit {
interval_ms: u64,
soft_trigger_bytes: usize,
hard_cap_bytes: usize,
},
}
impl Default for WalSyncMode {
fn default() -> Self {
WalSyncMode::GroupCommit {
interval_ms: 50,
soft_trigger_bytes: 2 * 1024 * 1024, hard_cap_bytes: 16 * 1024 * 1024, }
}
}
#[derive(Debug, Clone)]
pub struct DbOptions {
pub create_if_missing: bool,
pub memtable_flush_threshold: usize,
pub edge_uniqueness: bool,
pub dense_vector: Option<DenseVectorConfig>,
pub compact_after_n_flushes: u32,
pub wal_sync_mode: WalSyncMode,
pub memtable_hard_cap_bytes: usize,
pub max_immutable_memtables: usize,
}
impl Default for DbOptions {
fn default() -> Self {
DbOptions {
create_if_missing: true,
memtable_flush_threshold: 128 * 1024 * 1024, edge_uniqueness: false,
dense_vector: None,
compact_after_n_flushes: 4,
wal_sync_mode: WalSyncMode::default(),
memtable_hard_cap_bytes: 512 * 1024 * 1024, max_immutable_memtables: 4,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_db_options() {
let opts = DbOptions::default();
assert!(opts.create_if_missing);
assert_eq!(opts.memtable_flush_threshold, 128 * 1024 * 1024);
assert!(!opts.edge_uniqueness);
assert!(opts.dense_vector.is_none());
assert_eq!(opts.compact_after_n_flushes, 4);
assert!(matches!(
opts.wal_sync_mode,
WalSyncMode::GroupCommit {
interval_ms: 50,
soft_trigger_bytes: 2097152,
hard_cap_bytes: 16777216,
}
));
assert_eq!(opts.memtable_hard_cap_bytes, 512 * 1024 * 1024);
assert_eq!(opts.max_immutable_memtables, 4);
}
#[test]
fn test_prop_value_equality() {
assert_eq!(PropValue::Null, PropValue::Null);
assert_eq!(PropValue::Bool(true), PropValue::Bool(true));
assert_ne!(PropValue::Int(1), PropValue::Int(2));
assert_eq!(
PropValue::String("hello".to_string()),
PropValue::String("hello".to_string())
);
assert_eq!(
PropValue::Array(vec![PropValue::Int(1), PropValue::Int(2)]),
PropValue::Array(vec![PropValue::Int(1), PropValue::Int(2)])
);
}
#[test]
fn test_prop_value_map() {
let mut inner = BTreeMap::new();
inner.insert("nested_key".to_string(), PropValue::Int(42));
inner.insert("flag".to_string(), PropValue::Bool(true));
let map = PropValue::Map(inner.clone());
assert_eq!(map, PropValue::Map(inner));
}
#[test]
#[allow(clippy::approx_constant)]
fn test_prop_value_map_msgpack_roundtrip() {
let mut inner = BTreeMap::new();
inner.insert("x".to_string(), PropValue::Float(3.14));
inner.insert("label".to_string(), PropValue::String("hello".into()));
inner.insert(
"items".to_string(),
PropValue::Array(vec![PropValue::Int(1), PropValue::Int(2)]),
);
let mut nested = BTreeMap::new();
nested.insert("deep".to_string(), PropValue::Bool(false));
inner.insert("child".to_string(), PropValue::Map(nested));
let map = PropValue::Map(inner);
let bytes = rmp_serde::to_vec(&map).expect("serialize");
let decoded: PropValue = rmp_serde::from_slice(&bytes).expect("deserialize");
assert_eq!(map, decoded);
}
#[test]
fn test_op_tag_roundtrip() {
for tag_val in 1u8..=4 {
let tag = OpTag::from_u8(tag_val).unwrap();
assert_eq!(tag as u8, tag_val);
}
assert!(OpTag::from_u8(0).is_none());
assert_eq!(OpTag::from_u8(5), Some(OpTag::EnsureNodeLabel));
assert_eq!(OpTag::from_u8(6), Some(OpTag::EnsureEdgeLabel));
assert_eq!(OpTag::from_u8(7), Some(OpTag::BeginAtomicBatch));
assert_eq!(OpTag::from_u8(8), Some(OpTag::CommitAtomicBatch));
assert!(OpTag::from_u8(255).is_none());
}
#[test]
fn test_node_label_set_canonicalizes_distinct_ids() {
let set = NodeLabelSet::from_label_ids([7, 3, 5]).unwrap();
assert_eq!(set.len(), 3);
assert_eq!(set.as_slice(), &[3, 5, 7]);
assert!(set.contains(5));
assert!(!set.contains(4));
assert!(set.contains_all(&NodeLabelSet::from_label_ids([3, 7]).unwrap()));
assert!(set.contains_any(&NodeLabelSet::from_label_ids([2, 7]).unwrap()));
assert!(!set.contains_any(&NodeLabelSet::from_label_ids([1, 2]).unwrap()));
assert_eq!(NodeLabelSet::single(9).unwrap().as_slice(), &[9]);
}
#[test]
fn test_node_label_set_rejects_empty_duplicate_zero_and_too_many_ids() {
assert!(NodeLabelSet::from_label_ids([]).is_err());
assert!(NodeLabelSet::from_label_ids([3, 3]).is_err());
assert!(NodeLabelSet::from_label_ids([0]).is_err());
assert!(NodeLabelSet::from_label_ids(1..=11).is_err());
}
#[test]
fn test_node_label_set_canonical_decoder_rejects_unsorted_and_duplicates() {
assert_eq!(
NodeLabelSet::from_canonical_ids(&[1, 3, 5])
.unwrap()
.as_slice(),
&[1, 3, 5]
);
assert!(NodeLabelSet::from_canonical_ids(&[]).is_err());
assert!(NodeLabelSet::from_canonical_ids(&[2, 1]).is_err());
assert!(NodeLabelSet::from_canonical_ids(&[2, 2]).is_err());
assert!(NodeLabelSet::from_canonical_ids(&(1..=11).collect::<Vec<_>>()).is_err());
}
#[test]
fn test_public_node_label_list_validation() {
validate_public_node_label_list(["Person", "Employee"]).unwrap();
assert!(validate_public_node_label_list(std::iter::empty::<&str>()).is_err());
assert!(validate_public_node_label_list(["Person", "Person"]).is_err());
assert!(validate_public_node_label_list([" Person"]).is_err());
assert!(validate_public_node_label_list([
"L1", "L2", "L3", "L4", "L5", "L6", "L7", "L8", "L9", "L10", "L11"
])
.is_err());
}
#[test]
fn test_node_label_filter_validation_rejects_empty_and_duplicate_labels() {
validate_node_label_filter(&NodeLabelFilter {
labels: vec!["Person".to_string(), "Employee".to_string()],
mode: LabelMatchMode::All,
})
.unwrap();
assert!(validate_node_label_filter(&NodeLabelFilter {
labels: Vec::new(),
mode: LabelMatchMode::Any,
})
.is_err());
assert!(validate_node_label_filter(&NodeLabelFilter {
labels: vec!["Person".to_string(), "Person".to_string()],
mode: LabelMatchMode::Any,
})
.is_err());
}
#[test]
fn test_direction_serde_roundtrip() {
for dir in [Direction::Outgoing, Direction::Incoming, Direction::Both] {
let json = serde_json::to_string(&dir).unwrap();
let back: Direction = serde_json::from_str(&json).unwrap();
assert_eq!(dir, back);
}
}
#[test]
fn test_neighbor_entry_serde_roundtrip() {
let entry = NeighborEntry {
node_id: 42,
edge_id: 99,
label: "FRIENDS_WITH".to_string(),
weight: 0.75,
valid_from: 1000,
valid_to: i64::MAX,
};
let json = serde_json::to_string(&entry).unwrap();
let back: NeighborEntry = serde_json::from_str(&json).unwrap();
assert_eq!(entry, back);
}
#[test]
fn test_manifest_state_serde() {
let state = ManifestState {
version: 1,
label_token_schema_version: LABEL_TOKEN_SCHEMA_VERSION,
node_label_tokens: BTreeMap::new(),
edge_label_tokens: BTreeMap::new(),
next_node_label_id: 1,
next_edge_label_id: 1,
segments: vec![
SegmentInfo {
id: 1,
node_count: 100,
edge_count: 200,
segment_format_version: 10,
segment_data_id: [1; 32],
},
SegmentInfo {
id: 2,
node_count: 50,
edge_count: 75,
segment_format_version: 10,
segment_data_id: [2; 32],
},
],
next_node_id: 151,
next_edge_id: 276,
dense_vector: Some(DenseVectorConfig {
dimension: 384,
metric: DenseMetric::Cosine,
hnsw: HnswConfig::default(),
}),
prune_policies: BTreeMap::new(),
next_engine_seq: 0,
next_wal_generation_id: 0,
active_wal_generation_id: 0,
pending_flush_epochs: Vec::new(),
secondary_indexes: Vec::new(),
next_secondary_index_id: 1,
};
let json = serde_json::to_string(&state).unwrap();
let loaded: ManifestState = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.version, 1);
assert_eq!(loaded.segments.len(), 2);
assert_eq!(loaded.next_node_id, 151);
assert_eq!(loaded.next_edge_id, 276);
assert_eq!(loaded.dense_vector, state.dense_vector);
}
#[test]
fn test_validate_dense_vector_config_rejects_invalid_values() {
let err = validate_dense_vector_config(&DenseVectorConfig {
dimension: 0,
metric: DenseMetric::Cosine,
hnsw: HnswConfig::default(),
})
.unwrap_err();
assert!(matches!(err, EngineError::InvalidOperation(_)));
let err = validate_dense_vector_config(&DenseVectorConfig {
dimension: 8,
metric: DenseMetric::Cosine,
hnsw: HnswConfig {
m: 32,
ef_construction: 16,
},
})
.unwrap_err();
assert!(matches!(err, EngineError::InvalidOperation(_)));
}
#[test]
fn test_validate_dense_vector_rejects_wrong_length_and_non_finite_values() {
let config = DenseVectorConfig {
dimension: 3,
metric: DenseMetric::DotProduct,
hnsw: HnswConfig::default(),
};
let err = validate_dense_vector(&[1.0, 2.0], &config).unwrap_err();
assert!(matches!(err, EngineError::InvalidOperation(_)));
let err = validate_dense_vector(&[1.0, f32::NAN, 3.0], &config).unwrap_err();
assert!(matches!(err, EngineError::InvalidOperation(_)));
}
#[test]
fn test_canonicalize_sparse_vector_sorts_merges_and_drops_zeros() {
let canonical = canonicalize_sparse_vector(&[
(9, 0.0),
(4, 1.5),
(2, 2.0),
(4, 0.5),
(2, 0.0),
(7, 3.0),
(4, 1.0),
])
.unwrap()
.unwrap();
assert_eq!(canonical, vec![(2, 2.0), (4, 3.0), (7, 3.0)]);
}
#[test]
fn test_canonicalize_sparse_vector_rejects_non_finite_values() {
let err = canonicalize_sparse_vector(&[(1, f32::INFINITY)]).unwrap_err();
assert!(matches!(err, EngineError::InvalidOperation(_)));
}
#[test]
fn test_canonicalize_sparse_vector_rejects_negative_values() {
let err = canonicalize_sparse_vector(&[(1, -0.25)]).unwrap_err();
assert!(matches!(err, EngineError::InvalidOperation(_)));
assert!(err
.to_string()
.contains("sparse vector weights must be non-negative"));
}
#[test]
fn test_upsert_node_options_default() {
let opts = UpsertNodeOptions::default();
assert!(opts.props.is_empty());
assert_eq!(opts.weight, 1.0);
assert!(opts.dense_vector.is_none());
assert!(opts.sparse_vector.is_none());
}
#[test]
fn test_upsert_edge_options_default() {
let opts = UpsertEdgeOptions::default();
assert!(opts.props.is_empty());
assert_eq!(opts.weight, 1.0);
assert!(opts.valid_from.is_none());
assert!(opts.valid_to.is_none());
}
#[test]
fn test_neighbor_options_default() {
let opts = NeighborOptions::default();
assert_eq!(opts.direction, Direction::Outgoing);
assert!(opts.edge_label_filter.is_none());
assert!(opts.limit.is_none());
assert!(opts.at_epoch.is_none());
assert!(opts.decay_lambda.is_none());
}
#[test]
fn test_degree_options_default() {
let opts = DegreeOptions::default();
assert_eq!(opts.direction, Direction::Outgoing);
assert!(opts.edge_label_filter.is_none());
assert!(opts.at_epoch.is_none());
}
#[test]
fn test_traverse_options_default() {
let opts = TraverseOptions::default();
assert_eq!(opts.min_depth, 1);
assert_eq!(opts.direction, Direction::Outgoing);
assert!(opts.edge_label_filter.is_none());
assert!(opts.emit_node_label_filter.is_none());
assert!(opts.at_epoch.is_none());
assert!(opts.decay_lambda.is_none());
assert!(opts.limit.is_none());
assert!(opts.cursor.is_none());
}
#[test]
fn test_subgraph_options_default() {
let opts = SubgraphOptions::default();
assert_eq!(opts.direction, Direction::Outgoing);
assert!(opts.edge_label_filter.is_none());
assert!(opts.node_label_filter.is_none());
assert!(opts.at_epoch.is_none());
}
#[test]
fn test_shortest_path_options_default() {
let opts = ShortestPathOptions::default();
assert_eq!(opts.direction, Direction::Outgoing);
assert!(opts.edge_label_filter.is_none());
assert!(opts.weight_field.is_none());
assert!(opts.at_epoch.is_none());
assert!(opts.max_depth.is_none());
assert!(opts.max_cost.is_none());
}
#[test]
fn test_component_options_default() {
let opts = ComponentOptions::default();
assert!(opts.edge_label_filter.is_none());
assert!(opts.node_label_filter.is_none());
assert!(opts.at_epoch.is_none());
}
#[test]
fn test_page_request_default() {
let req = PageRequest::default();
assert!(req.limit.is_none());
assert!(req.after.is_none());
}
#[test]
fn test_page_result_last_page() {
let result: PageResult<u64> = PageResult {
items: vec![1, 2, 3],
next_cursor: None,
};
assert_eq!(result.items.len(), 3);
assert!(result.next_cursor.is_none());
}
#[test]
fn test_page_result_has_more() {
let result: PageResult<u64> = PageResult {
items: vec![1, 2, 3],
next_cursor: Some(3),
};
assert_eq!(result.items.len(), 3);
assert_eq!(result.next_cursor, Some(3));
}
}