Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,413 @@
//! Abstract Syntax Tree definitions for Cypher query language
//!
//! Represents the parsed structure of Cypher queries including:
//! - Pattern matching (MATCH, OPTIONAL MATCH)
//! - Filtering (WHERE)
//! - Projections (RETURN, WITH)
//! - Mutations (CREATE, MERGE, DELETE, SET)
//! - Aggregations and ordering
//! - Hyperedge support for N-ary relationships
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Top-level query representation
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Query {
pub statements: Vec<Statement>,
}
/// Individual query statement
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Statement {
Match(MatchClause),
Create(CreateClause),
Merge(MergeClause),
Delete(DeleteClause),
Set(SetClause),
Remove(RemoveClause),
Return(ReturnClause),
With(WithClause),
}
/// MATCH clause for pattern matching
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MatchClause {
pub optional: bool,
pub patterns: Vec<Pattern>,
pub where_clause: Option<WhereClause>,
}
/// Pattern matching expressions
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Pattern {
/// Simple node pattern: (n:Label {props})
Node(NodePattern),
/// Relationship pattern: (a)-[r:TYPE]->(b)
Relationship(RelationshipPattern),
/// Path pattern: p = (a)-[*1..5]->(b)
Path(PathPattern),
/// Hyperedge pattern for N-ary relationships: (a)-[r:TYPE]->(b,c,d)
Hyperedge(HyperedgePattern),
}
/// Node pattern: (variable:Label {property: value})
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NodePattern {
pub variable: Option<String>,
pub labels: Vec<String>,
pub properties: Option<PropertyMap>,
}
/// Relationship pattern: [variable:Type {properties}]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelationshipPattern {
pub variable: Option<String>,
pub rel_type: Option<String>,
pub properties: Option<PropertyMap>,
pub direction: Direction,
pub range: Option<RelationshipRange>,
/// Source node pattern
pub from: Box<NodePattern>,
/// Target - can be a NodePattern or another Pattern for chained relationships
/// For simple relationships like (a)-[r]->(b), this is just the node
/// For chained patterns like (a)-[r]->(b)<-[s]-(c), the target is nested
pub to: Box<Pattern>,
}
/// Hyperedge pattern for N-ary relationships
/// Example: (person)-[r:TRANSACTION]->(account1, account2, merchant)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperedgePattern {
pub variable: Option<String>,
pub rel_type: String,
pub properties: Option<PropertyMap>,
pub from: Box<NodePattern>,
pub to: Vec<NodePattern>, // Multiple target nodes for N-ary relationships
pub arity: usize, // Number of participating nodes (including source)
}
/// Relationship direction
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Direction {
Outgoing, // ->
Incoming, // <-
Undirected, // -
}
/// Relationship range for path queries: [*min..max]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RelationshipRange {
pub min: Option<usize>,
pub max: Option<usize>,
}
/// Path pattern: p = (a)-[*]->(b)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PathPattern {
pub variable: String,
pub pattern: Box<Pattern>,
}
/// Property map: {key: value, ...}
pub type PropertyMap = HashMap<String, Expression>;
/// WHERE clause for filtering
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WhereClause {
pub condition: Expression,
}
/// CREATE clause for creating nodes and relationships
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateClause {
pub patterns: Vec<Pattern>,
}
/// MERGE clause for create-or-match
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MergeClause {
pub pattern: Pattern,
pub on_create: Option<SetClause>,
pub on_match: Option<SetClause>,
}
/// DELETE clause
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeleteClause {
pub detach: bool,
pub expressions: Vec<Expression>,
}
/// SET clause for updating properties
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SetClause {
pub items: Vec<SetItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SetItem {
Property {
variable: String,
property: String,
value: Expression,
},
Variable {
variable: String,
value: Expression,
},
Labels {
variable: String,
labels: Vec<String>,
},
}
/// REMOVE clause for removing properties or labels
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RemoveClause {
pub items: Vec<RemoveItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RemoveItem {
/// Remove a property: REMOVE n.property
Property { variable: String, property: String },
/// Remove labels: REMOVE n:Label1:Label2
Labels {
variable: String,
labels: Vec<String>,
},
}
/// RETURN clause for projection
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReturnClause {
pub distinct: bool,
pub items: Vec<ReturnItem>,
pub order_by: Option<OrderBy>,
pub skip: Option<Expression>,
pub limit: Option<Expression>,
}
/// WITH clause for chaining queries
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WithClause {
pub distinct: bool,
pub items: Vec<ReturnItem>,
pub where_clause: Option<WhereClause>,
pub order_by: Option<OrderBy>,
pub skip: Option<Expression>,
pub limit: Option<Expression>,
}
/// Return item: expression AS alias
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReturnItem {
pub expression: Expression,
pub alias: Option<String>,
}
/// ORDER BY clause
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrderBy {
pub items: Vec<OrderByItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrderByItem {
pub expression: Expression,
pub ascending: bool,
}
/// Expression tree
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Expression {
// Literals
Integer(i64),
Float(f64),
String(String),
Boolean(bool),
Null,
// Variables and properties
Variable(String),
Property {
object: Box<Expression>,
property: String,
},
// Collections
List(Vec<Expression>),
Map(HashMap<String, Expression>),
// Operators
BinaryOp {
left: Box<Expression>,
op: BinaryOperator,
right: Box<Expression>,
},
UnaryOp {
op: UnaryOperator,
operand: Box<Expression>,
},
// Functions and aggregations
FunctionCall {
name: String,
args: Vec<Expression>,
},
Aggregation {
function: AggregationFunction,
expression: Box<Expression>,
distinct: bool,
},
// Pattern predicates
PatternPredicate(Box<Pattern>),
// Case expressions
Case {
expression: Option<Box<Expression>>,
alternatives: Vec<(Expression, Expression)>,
default: Option<Box<Expression>>,
},
}
/// Binary operators
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOperator {
// Arithmetic
Add,
Subtract,
Multiply,
Divide,
Modulo,
Power,
// Comparison
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
// Logical
And,
Or,
Xor,
// String
Contains,
StartsWith,
EndsWith,
Matches, // Regex
// Collection
In,
// Null checking
Is,
IsNot,
}
/// Unary operators
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOperator {
Not,
Minus,
Plus,
IsNull,
IsNotNull,
}
/// Aggregation functions
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AggregationFunction {
Count,
Sum,
Avg,
Min,
Max,
Collect,
StdDev,
StdDevP,
Percentile,
}
impl Query {
pub fn new(statements: Vec<Statement>) -> Self {
Self { statements }
}
/// Check if query contains only read operations
pub fn is_read_only(&self) -> bool {
self.statements.iter().all(|stmt| {
matches!(
stmt,
Statement::Match(_) | Statement::Return(_) | Statement::With(_)
)
})
}
/// Check if query contains hyperedges
pub fn has_hyperedges(&self) -> bool {
self.statements.iter().any(|stmt| match stmt {
Statement::Match(m) => m
.patterns
.iter()
.any(|p| matches!(p, Pattern::Hyperedge(_))),
Statement::Create(c) => c
.patterns
.iter()
.any(|p| matches!(p, Pattern::Hyperedge(_))),
Statement::Merge(m) => matches!(&m.pattern, Pattern::Hyperedge(_)),
_ => false,
})
}
}
impl Pattern {
/// Get the arity of the pattern (number of nodes involved)
pub fn arity(&self) -> usize {
match self {
Pattern::Node(_) => 1,
Pattern::Relationship(_) => 2,
Pattern::Path(_) => 2, // Simplified, could be variable
Pattern::Hyperedge(h) => h.arity,
}
}
}
impl Expression {
/// Check if expression is constant (no variables)
pub fn is_constant(&self) -> bool {
match self {
Expression::Integer(_)
| Expression::Float(_)
| Expression::String(_)
| Expression::Boolean(_)
| Expression::Null => true,
Expression::List(items) => items.iter().all(|e| e.is_constant()),
Expression::Map(map) => map.values().all(|e| e.is_constant()),
Expression::BinaryOp { left, right, .. } => left.is_constant() && right.is_constant(),
Expression::UnaryOp { operand, .. } => operand.is_constant(),
_ => false,
}
}
/// Check if expression contains aggregation
pub fn has_aggregation(&self) -> bool {
match self {
Expression::Aggregation { .. } => true,
Expression::BinaryOp { left, right, .. } => {
left.has_aggregation() || right.has_aggregation()
}
Expression::UnaryOp { operand, .. } => operand.has_aggregation(),
Expression::FunctionCall { args, .. } => args.iter().any(|e| e.has_aggregation()),
Expression::List(items) => items.iter().any(|e| e.has_aggregation()),
Expression::Property { object, .. } => object.has_aggregation(),
_ => false,
}
}
}

View File

@@ -0,0 +1,616 @@
//! Cypher query executor for in-memory property graph
use super::ast::*;
use super::graph_store::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ExecutionError {
#[error("Graph error: {0}")]
GraphError(#[from] GraphError),
#[error("Variable not found: {0}")]
VariableNotFound(String),
#[error("Type error: {0}")]
TypeError(String),
#[error("Unsupported operation: {0}")]
UnsupportedOperation(String),
#[error("Execution error: {0}")]
ExecutionError(String),
}
/// Execution context holding variable bindings
#[derive(Debug, Clone)]
pub struct ExecutionContext {
pub variables: HashMap<String, ContextValue>,
}
impl ExecutionContext {
pub fn new() -> Self {
Self {
variables: HashMap::new(),
}
}
pub fn bind(&mut self, name: String, value: ContextValue) {
self.variables.insert(name, value);
}
pub fn get(&self, name: &str) -> Option<&ContextValue> {
self.variables.get(name)
}
}
impl Default for ExecutionContext {
fn default() -> Self {
Self::new()
}
}
/// Value in execution context (node, edge, or property value)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ContextValue {
Node(Node),
Edge(Edge),
Value(Value),
List(Vec<ContextValue>),
Map(HashMap<String, ContextValue>),
}
impl ContextValue {
pub fn as_node(&self) -> Option<&Node> {
match self {
ContextValue::Node(n) => Some(n),
_ => None,
}
}
pub fn as_value(&self) -> Option<&Value> {
match self {
ContextValue::Value(v) => Some(v),
_ => None,
}
}
}
/// Query execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionResult {
pub columns: Vec<String>,
pub rows: Vec<HashMap<String, ContextValue>>,
}
impl ExecutionResult {
pub fn new(columns: Vec<String>) -> Self {
Self {
columns,
rows: Vec::new(),
}
}
pub fn add_row(&mut self, row: HashMap<String, ContextValue>) {
self.rows.push(row);
}
}
/// Cypher query executor
pub struct Executor<'a> {
graph: &'a mut PropertyGraph,
}
impl<'a> Executor<'a> {
pub fn new(graph: &'a mut PropertyGraph) -> Self {
Self { graph }
}
/// Execute a parsed Cypher query
pub fn execute(&mut self, query: &Query) -> Result<ExecutionResult, ExecutionError> {
let mut context = ExecutionContext::new();
let mut result = None;
for statement in &query.statements {
result = Some(self.execute_statement(statement, &mut context)?);
}
result.ok_or_else(|| ExecutionError::ExecutionError("No statements to execute".to_string()))
}
fn execute_statement(
&mut self,
statement: &Statement,
context: &mut ExecutionContext,
) -> Result<ExecutionResult, ExecutionError> {
match statement {
Statement::Create(clause) => self.execute_create(clause, context),
Statement::Match(clause) => self.execute_match(clause, context),
Statement::Return(clause) => self.execute_return(clause, context),
Statement::Set(clause) => self.execute_set(clause, context),
Statement::Delete(clause) => self.execute_delete(clause, context),
_ => Err(ExecutionError::UnsupportedOperation(format!(
"Statement {:?} not yet implemented",
statement
))),
}
}
fn execute_create(
&mut self,
clause: &CreateClause,
context: &mut ExecutionContext,
) -> Result<ExecutionResult, ExecutionError> {
for pattern in &clause.patterns {
self.create_pattern(pattern, context)?;
}
Ok(ExecutionResult::new(vec![]))
}
fn create_pattern(
&mut self,
pattern: &Pattern,
context: &mut ExecutionContext,
) -> Result<(), ExecutionError> {
match pattern {
Pattern::Node(node_pattern) => {
let node = self.create_node(node_pattern)?;
if let Some(var) = &node_pattern.variable {
context.bind(var.clone(), ContextValue::Node(node));
}
Ok(())
}
Pattern::Relationship(rel_pattern) => {
self.create_relationship(rel_pattern, context)?;
Ok(())
}
_ => Err(ExecutionError::UnsupportedOperation(
"Only simple node and relationship patterns supported in CREATE".to_string(),
)),
}
}
fn create_node(&mut self, pattern: &NodePattern) -> Result<Node, ExecutionError> {
let id = self.graph.generate_node_id();
let mut node = Node::new(id).with_labels(pattern.labels.clone());
// Set properties
if let Some(props) = &pattern.properties {
for (key, expr) in props {
let value = self.evaluate_expression(expr, &ExecutionContext::new())?;
node.set_property(key.clone(), value);
}
}
let node_id = self.graph.add_node(node.clone());
node.id = node_id;
Ok(node)
}
fn create_relationship(
&mut self,
pattern: &RelationshipPattern,
context: &mut ExecutionContext,
) -> Result<(), ExecutionError> {
// Get or create source node
let from_node = if let Some(var) = &pattern.from.variable {
if let Some(ContextValue::Node(n)) = context.get(var) {
n.clone()
} else {
self.create_node(&pattern.from)?
}
} else {
self.create_node(&pattern.from)?
};
// Get or create target node (only handle simple node targets for now)
let to_node = match &*pattern.to {
Pattern::Node(node_pattern) => {
if let Some(var) = &node_pattern.variable {
if let Some(ContextValue::Node(n)) = context.get(var) {
n.clone()
} else {
self.create_node(node_pattern)?
}
} else {
self.create_node(node_pattern)?
}
}
_ => {
return Err(ExecutionError::UnsupportedOperation(
"Complex relationship targets not yet supported".to_string(),
))
}
};
// Create the edge
let edge_type = pattern
.rel_type
.clone()
.unwrap_or_else(|| "RELATED_TO".to_string());
let edge_id = self.graph.generate_edge_id();
let mut edge = Edge::new(edge_id, from_node.id.clone(), to_node.id.clone(), edge_type);
// Set properties
if let Some(props) = &pattern.properties {
for (key, expr) in props {
let value = self.evaluate_expression(expr, context)?;
edge.set_property(key.clone(), value);
}
}
let edge_id = self.graph.add_edge(edge.clone())?;
if let Some(var) = &pattern.variable {
edge.id = edge_id;
context.bind(var.clone(), ContextValue::Edge(edge));
}
Ok(())
}
fn execute_match(
&mut self,
clause: &MatchClause,
context: &mut ExecutionContext,
) -> Result<ExecutionResult, ExecutionError> {
let mut matches = Vec::new();
for pattern in &clause.patterns {
let pattern_matches = self.match_pattern(pattern)?;
matches.extend(pattern_matches);
}
// Apply WHERE filter if present
if let Some(where_clause) = &clause.where_clause {
matches.retain(|ctx| {
self.evaluate_condition(&where_clause.condition, ctx)
.unwrap_or(false)
});
}
// Merge matches into context
for match_ctx in matches {
for (var, val) in match_ctx.variables {
context.bind(var, val);
}
}
Ok(ExecutionResult::new(vec![]))
}
fn match_pattern(&self, pattern: &Pattern) -> Result<Vec<ExecutionContext>, ExecutionError> {
match pattern {
Pattern::Node(node_pattern) => self.match_node_pattern(node_pattern),
Pattern::Relationship(rel_pattern) => self.match_relationship_pattern(rel_pattern),
_ => Err(ExecutionError::UnsupportedOperation(
"Pattern type not yet supported in MATCH".to_string(),
)),
}
}
fn match_node_pattern(
&self,
pattern: &NodePattern,
) -> Result<Vec<ExecutionContext>, ExecutionError> {
let mut contexts = Vec::new();
// Find nodes matching labels
let candidates: Vec<&Node> = if pattern.labels.is_empty() {
self.graph.find_nodes(|_| true)
} else {
let mut nodes = Vec::new();
for label in &pattern.labels {
nodes.extend(self.graph.find_nodes_by_label(label));
}
nodes
};
// Filter by properties
for node in candidates {
if let Some(props) = &pattern.properties {
let mut matches = true;
for (key, expr) in props {
let expected_value =
self.evaluate_expression(expr, &ExecutionContext::new())?;
if node.get_property(key) != Some(&expected_value) {
matches = false;
break;
}
}
if !matches {
continue;
}
}
let mut ctx = ExecutionContext::new();
if let Some(var) = &pattern.variable {
ctx.bind(var.clone(), ContextValue::Node(node.clone()));
}
contexts.push(ctx);
}
Ok(contexts)
}
fn match_relationship_pattern(
&self,
pattern: &RelationshipPattern,
) -> Result<Vec<ExecutionContext>, ExecutionError> {
let mut contexts = Vec::new();
// Match source nodes
let from_contexts = self.match_node_pattern(&pattern.from)?;
for from_ctx in from_contexts {
// Get the source node
let from_node = if let Some(var) = &pattern.from.variable {
from_ctx
.get(var)
.and_then(|v| v.as_node())
.ok_or_else(|| ExecutionError::VariableNotFound(var.clone()))?
} else {
continue;
};
// Find matching edges
let edges = match pattern.direction {
Direction::Outgoing => self.graph.get_outgoing_edges(&from_node.id),
Direction::Incoming => self.graph.get_incoming_edges(&from_node.id),
Direction::Undirected => {
let mut all = self.graph.get_outgoing_edges(&from_node.id);
all.extend(self.graph.get_incoming_edges(&from_node.id));
all
}
};
for edge in edges {
// Filter by type
if let Some(rel_type) = &pattern.rel_type {
if &edge.edge_type != rel_type {
continue;
}
}
// Filter by properties
if let Some(props) = &pattern.properties {
let mut matches = true;
for (key, expr) in props {
let expected_value =
self.evaluate_expression(expr, &ExecutionContext::new())?;
if edge.get_property(key) != Some(&expected_value) {
matches = false;
break;
}
}
if !matches {
continue;
}
}
// Get target node
let to_node_id = if pattern.direction == Direction::Incoming {
&edge.from
} else {
&edge.to
};
if let Some(to_node) = self.graph.get_node(to_node_id) {
let mut ctx = from_ctx.clone();
if let Some(var) = &pattern.variable {
ctx.bind(var.clone(), ContextValue::Edge(edge.clone()));
}
// Bind target node if it's a simple node pattern
if let Pattern::Node(to_pattern) = &*pattern.to {
if let Some(var) = &to_pattern.variable {
ctx.bind(var.clone(), ContextValue::Node(to_node.clone()));
}
}
contexts.push(ctx);
}
}
}
Ok(contexts)
}
fn execute_return(
&self,
clause: &ReturnClause,
context: &ExecutionContext,
) -> Result<ExecutionResult, ExecutionError> {
let mut columns = Vec::new();
let mut row = HashMap::new();
for item in &clause.items {
let col_name = item
.alias
.clone()
.unwrap_or_else(|| match &item.expression {
Expression::Variable(var) => var.clone(),
_ => "?column?".to_string(),
});
columns.push(col_name.clone());
let value = self.evaluate_expression_ctx(&item.expression, context)?;
row.insert(col_name, value);
}
let mut result = ExecutionResult::new(columns);
result.add_row(row);
Ok(result)
}
fn execute_set(
&mut self,
clause: &SetClause,
context: &ExecutionContext,
) -> Result<ExecutionResult, ExecutionError> {
for item in &clause.items {
match item {
SetItem::Property {
variable,
property,
value,
} => {
let val = self.evaluate_expression(value, context)?;
if let Some(ContextValue::Node(node)) = context.get(variable) {
if let Some(node_mut) = self.graph.get_node_mut(&node.id) {
node_mut.set_property(property.clone(), val);
}
}
}
_ => {
return Err(ExecutionError::UnsupportedOperation(
"Only property SET supported".to_string(),
))
}
}
}
Ok(ExecutionResult::new(vec![]))
}
fn execute_delete(
&mut self,
clause: &DeleteClause,
context: &ExecutionContext,
) -> Result<ExecutionResult, ExecutionError> {
for expr in &clause.expressions {
if let Expression::Variable(var) = expr {
if let Some(ctx_val) = context.get(var) {
match ctx_val {
ContextValue::Node(node) => {
if clause.detach {
self.graph.delete_node(&node.id)?;
} else {
return Err(ExecutionError::ExecutionError(
"Cannot delete node with relationships without DETACH"
.to_string(),
));
}
}
ContextValue::Edge(edge) => {
self.graph.delete_edge(&edge.id)?;
}
_ => {}
}
}
}
}
Ok(ExecutionResult::new(vec![]))
}
fn evaluate_expression(
&self,
expr: &Expression,
context: &ExecutionContext,
) -> Result<Value, ExecutionError> {
match expr {
Expression::Integer(n) => Ok(Value::Integer(*n)),
Expression::Float(f) => Ok(Value::Float(*f)),
Expression::String(s) => Ok(Value::String(s.clone())),
Expression::Boolean(b) => Ok(Value::Boolean(*b)),
Expression::Null => Ok(Value::Null),
Expression::Variable(var) => {
if let Some(ContextValue::Value(v)) = context.get(var) {
Ok(v.clone())
} else {
Err(ExecutionError::VariableNotFound(var.clone()))
}
}
Expression::Property { object, property } => {
if let Expression::Variable(var) = &**object {
if let Some(ContextValue::Node(node)) = context.get(var) {
Ok(node.get_property(property).cloned().unwrap_or(Value::Null))
} else {
Err(ExecutionError::VariableNotFound(var.clone()))
}
} else {
Err(ExecutionError::UnsupportedOperation(
"Nested property access not supported".to_string(),
))
}
}
_ => Err(ExecutionError::UnsupportedOperation(format!(
"Expression {:?} not yet implemented",
expr
))),
}
}
fn evaluate_expression_ctx(
&self,
expr: &Expression,
context: &ExecutionContext,
) -> Result<ContextValue, ExecutionError> {
match expr {
Expression::Variable(var) => context
.get(var)
.cloned()
.ok_or_else(|| ExecutionError::VariableNotFound(var.clone())),
Expression::Property { object, property } => {
if let Expression::Variable(var) = &**object {
if let Some(ContextValue::Node(node)) = context.get(var) {
Ok(ContextValue::Value(
node.get_property(property).cloned().unwrap_or(Value::Null),
))
} else {
Err(ExecutionError::VariableNotFound(var.clone()))
}
} else {
Err(ExecutionError::UnsupportedOperation(
"Nested property access not supported".to_string(),
))
}
}
_ => {
let val = self.evaluate_expression(expr, context)?;
Ok(ContextValue::Value(val))
}
}
}
fn evaluate_condition(
&self,
expr: &Expression,
context: &ExecutionContext,
) -> Result<bool, ExecutionError> {
match expr {
Expression::Boolean(b) => Ok(*b),
Expression::BinaryOp { left, op, right } => {
let left_val = self.evaluate_expression(left, context)?;
let right_val = self.evaluate_expression(right, context)?;
match op {
BinaryOperator::Equal => Ok(left_val == right_val),
BinaryOperator::NotEqual => Ok(left_val != right_val),
BinaryOperator::GreaterThan => {
if let (Some(l), Some(r)) = (left_val.as_i64(), right_val.as_i64()) {
Ok(l > r)
} else {
Ok(false)
}
}
BinaryOperator::LessThan => {
if let (Some(l), Some(r)) = (left_val.as_i64(), right_val.as_i64()) {
Ok(l < r)
} else {
Ok(false)
}
}
_ => Err(ExecutionError::UnsupportedOperation(format!(
"Operator {:?} not implemented",
op
))),
}
}
_ => Err(ExecutionError::UnsupportedOperation(
"Complex conditions not yet supported".to_string(),
)),
}
}
}

View File

@@ -0,0 +1,438 @@
//! In-memory property graph storage for WASM-compatible Cypher execution
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
pub type NodeId = String;
pub type EdgeId = String;
#[derive(Debug, Error)]
pub enum GraphError {
#[error("Node not found: {0}")]
NodeNotFound(NodeId),
#[error("Edge not found: {0}")]
EdgeNotFound(EdgeId),
#[error("Invalid operation: {0}")]
InvalidOperation(String),
}
/// Property value that can be stored in nodes/edges
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Value {
Null,
Boolean(bool),
Integer(i64),
Float(f64),
String(String),
List(Vec<Value>),
Map(HashMap<String, Value>),
}
impl Value {
pub fn as_i64(&self) -> Option<i64> {
match self {
Value::Integer(n) => Some(*n),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Float(f) => Some(*f),
Value::Integer(i) => Some(*i as f64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Boolean(b) => Some(*b),
_ => None,
}
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Boolean(b)
}
}
impl From<i64> for Value {
fn from(n: i64) -> Self {
Value::Integer(n)
}
}
impl From<f64> for Value {
fn from(f: f64) -> Self {
Value::Float(f)
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(s)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::String(s.to_string())
}
}
/// Node in the property graph
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Node {
pub id: NodeId,
pub labels: Vec<String>,
pub properties: HashMap<String, Value>,
}
impl Node {
pub fn new(id: NodeId) -> Self {
Self {
id,
labels: Vec::new(),
properties: HashMap::new(),
}
}
pub fn with_label(mut self, label: String) -> Self {
self.labels.push(label);
self
}
pub fn with_labels(mut self, labels: Vec<String>) -> Self {
self.labels = labels;
self
}
pub fn with_property(mut self, key: String, value: Value) -> Self {
self.properties.insert(key, value);
self
}
pub fn has_label(&self, label: &str) -> bool {
self.labels.iter().any(|l| l == label)
}
pub fn get_property(&self, key: &str) -> Option<&Value> {
self.properties.get(key)
}
pub fn set_property(&mut self, key: String, value: Value) {
self.properties.insert(key, value);
}
pub fn remove_property(&mut self, key: &str) -> Option<Value> {
self.properties.remove(key)
}
pub fn add_label(&mut self, label: String) {
if !self.has_label(&label) {
self.labels.push(label);
}
}
pub fn remove_label(&mut self, label: &str) {
self.labels.retain(|l| l != label);
}
}
/// Edge/Relationship in the property graph
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Edge {
pub id: EdgeId,
pub from: NodeId,
pub to: NodeId,
pub edge_type: String,
pub properties: HashMap<String, Value>,
}
impl Edge {
pub fn new(id: EdgeId, from: NodeId, to: NodeId, edge_type: String) -> Self {
Self {
id,
from,
to,
edge_type,
properties: HashMap::new(),
}
}
pub fn with_property(mut self, key: String, value: Value) -> Self {
self.properties.insert(key, value);
self
}
pub fn get_property(&self, key: &str) -> Option<&Value> {
self.properties.get(key)
}
pub fn set_property(&mut self, key: String, value: Value) {
self.properties.insert(key, value);
}
}
/// In-memory property graph store
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertyGraph {
nodes: HashMap<NodeId, Node>,
edges: HashMap<EdgeId, Edge>,
// Indexes for faster lookups
label_index: HashMap<String, Vec<NodeId>>,
edge_type_index: HashMap<String, Vec<EdgeId>>,
outgoing_edges: HashMap<NodeId, Vec<EdgeId>>,
incoming_edges: HashMap<NodeId, Vec<EdgeId>>,
next_node_id: usize,
next_edge_id: usize,
}
impl PropertyGraph {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
edges: HashMap::new(),
label_index: HashMap::new(),
edge_type_index: HashMap::new(),
outgoing_edges: HashMap::new(),
incoming_edges: HashMap::new(),
next_node_id: 0,
next_edge_id: 0,
}
}
/// Generate a unique node ID
pub fn generate_node_id(&mut self) -> NodeId {
let id = format!("n{}", self.next_node_id);
self.next_node_id += 1;
id
}
/// Generate a unique edge ID
pub fn generate_edge_id(&mut self) -> EdgeId {
let id = format!("e{}", self.next_edge_id);
self.next_edge_id += 1;
id
}
/// Add a node to the graph
pub fn add_node(&mut self, node: Node) -> NodeId {
let id = node.id.clone();
// Update label index
for label in &node.labels {
self.label_index
.entry(label.clone())
.or_insert_with(Vec::new)
.push(id.clone());
}
self.nodes.insert(id.clone(), node);
id
}
/// Get a node by ID
pub fn get_node(&self, id: &NodeId) -> Option<&Node> {
self.nodes.get(id)
}
/// Get a mutable reference to a node
pub fn get_node_mut(&mut self, id: &NodeId) -> Option<&mut Node> {
self.nodes.get_mut(id)
}
/// Find nodes by label
pub fn find_nodes_by_label(&self, label: &str) -> Vec<&Node> {
if let Some(node_ids) = self.label_index.get(label) {
node_ids
.iter()
.filter_map(|id| self.nodes.get(id))
.collect()
} else {
Vec::new()
}
}
/// Find all nodes matching a predicate
pub fn find_nodes<F>(&self, predicate: F) -> Vec<&Node>
where
F: Fn(&Node) -> bool,
{
self.nodes.values().filter(|n| predicate(n)).collect()
}
/// Add an edge to the graph
pub fn add_edge(&mut self, edge: Edge) -> Result<EdgeId, GraphError> {
// Verify nodes exist
if !self.nodes.contains_key(&edge.from) {
return Err(GraphError::NodeNotFound(edge.from.clone()));
}
if !self.nodes.contains_key(&edge.to) {
return Err(GraphError::NodeNotFound(edge.to.clone()));
}
let id = edge.id.clone();
let from = edge.from.clone();
let to = edge.to.clone();
let edge_type = edge.edge_type.clone();
// Update indexes
self.edge_type_index
.entry(edge_type)
.or_insert_with(Vec::new)
.push(id.clone());
self.outgoing_edges
.entry(from)
.or_insert_with(Vec::new)
.push(id.clone());
self.incoming_edges
.entry(to)
.or_insert_with(Vec::new)
.push(id.clone());
self.edges.insert(id.clone(), edge);
Ok(id)
}
/// Get an edge by ID
pub fn get_edge(&self, id: &EdgeId) -> Option<&Edge> {
self.edges.get(id)
}
/// Get outgoing edges from a node
pub fn get_outgoing_edges(&self, node_id: &NodeId) -> Vec<&Edge> {
if let Some(edge_ids) = self.outgoing_edges.get(node_id) {
edge_ids
.iter()
.filter_map(|id| self.edges.get(id))
.collect()
} else {
Vec::new()
}
}
/// Get incoming edges to a node
pub fn get_incoming_edges(&self, node_id: &NodeId) -> Vec<&Edge> {
if let Some(edge_ids) = self.incoming_edges.get(node_id) {
edge_ids
.iter()
.filter_map(|id| self.edges.get(id))
.collect()
} else {
Vec::new()
}
}
/// Get all edges of a specific type
pub fn find_edges_by_type(&self, edge_type: &str) -> Vec<&Edge> {
if let Some(edge_ids) = self.edge_type_index.get(edge_type) {
edge_ids
.iter()
.filter_map(|id| self.edges.get(id))
.collect()
} else {
Vec::new()
}
}
/// Delete a node and its connected edges
pub fn delete_node(&mut self, id: &NodeId) -> Result<(), GraphError> {
let node = self
.nodes
.remove(id)
.ok_or_else(|| GraphError::NodeNotFound(id.clone()))?;
// Remove from label index
for label in &node.labels {
if let Some(ids) = self.label_index.get_mut(label) {
ids.retain(|nid| nid != id);
}
}
// Remove connected edges
if let Some(edge_ids) = self.outgoing_edges.remove(id) {
for edge_id in edge_ids {
self.edges.remove(&edge_id);
}
}
if let Some(edge_ids) = self.incoming_edges.remove(id) {
for edge_id in edge_ids {
self.edges.remove(&edge_id);
}
}
Ok(())
}
/// Delete an edge
pub fn delete_edge(&mut self, id: &EdgeId) -> Result<(), GraphError> {
let edge = self
.edges
.remove(id)
.ok_or_else(|| GraphError::EdgeNotFound(id.clone()))?;
// Remove from type index
if let Some(ids) = self.edge_type_index.get_mut(&edge.edge_type) {
ids.retain(|eid| eid != id);
}
// Remove from node edge lists
if let Some(ids) = self.outgoing_edges.get_mut(&edge.from) {
ids.retain(|eid| eid != id);
}
if let Some(ids) = self.incoming_edges.get_mut(&edge.to) {
ids.retain(|eid| eid != id);
}
Ok(())
}
/// Get statistics about the graph
pub fn stats(&self) -> GraphStats {
GraphStats {
node_count: self.nodes.len(),
edge_count: self.edges.len(),
label_count: self.label_index.len(),
edge_type_count: self.edge_type_index.len(),
}
}
/// Get all nodes in the graph
pub fn all_nodes(&self) -> Vec<&Node> {
self.nodes.values().collect()
}
/// Get all edges in the graph
pub fn all_edges(&self) -> Vec<&Edge> {
self.edges.values().collect()
}
}
impl Default for PropertyGraph {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphStats {
pub node_count: usize,
pub edge_count: usize,
pub label_count: usize,
pub edge_type_count: usize,
}

View File

@@ -0,0 +1,607 @@
//! Lexical analyzer (tokenizer) for Cypher query language
//!
//! Hand-rolled lexer for WASM compatibility - no external dependencies.
use serde::{Deserialize, Serialize};
use std::fmt;
use std::iter::Peekable;
use std::str::Chars;
/// Token with kind and location information
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Token {
pub kind: TokenKind,
pub lexeme: String,
pub position: Position,
}
/// Source position for error reporting
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Position {
pub line: usize,
pub column: usize,
pub offset: usize,
}
/// Token kinds
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TokenKind {
// Keywords
Match,
OptionalMatch,
Where,
Return,
Create,
Merge,
Delete,
DetachDelete,
Set,
Remove,
With,
OrderBy,
Limit,
Skip,
Distinct,
As,
Asc,
Desc,
Case,
When,
Then,
Else,
End,
And,
Or,
Xor,
Not,
In,
Is,
Null,
True,
False,
OnCreate,
OnMatch,
// Identifiers and literals
Identifier(String),
Integer(i64),
Float(f64),
String(String),
// Operators
Plus,
Minus,
Star,
Slash,
Percent,
Caret,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Arrow, // ->
LeftArrow, // <-
Dash, // -
// Delimiters
LeftParen,
RightParen,
LeftBracket,
RightBracket,
LeftBrace,
RightBrace,
Comma,
Dot,
Colon,
Semicolon,
Pipe,
// Special
DotDot, // ..
Eof,
}
impl fmt::Display for TokenKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenKind::Identifier(s) => write!(f, "identifier '{}'", s),
TokenKind::Integer(n) => write!(f, "integer {}", n),
TokenKind::Float(n) => write!(f, "float {}", n),
TokenKind::String(s) => write!(f, "string \"{}\"", s),
_ => write!(f, "{:?}", self),
}
}
}
/// Lexer error
#[derive(Debug, Clone)]
pub struct LexerError {
pub message: String,
pub position: Position,
}
impl fmt::Display for LexerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Lexer error at {}:{}: {}",
self.position.line, self.position.column, self.message
)
}
}
impl std::error::Error for LexerError {}
/// Hand-rolled Cypher lexer
pub struct Lexer<'a> {
input: &'a str,
chars: Peekable<Chars<'a>>,
position: Position,
current_offset: usize,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
Self {
input,
chars: input.chars().peekable(),
position: Position {
line: 1,
column: 1,
offset: 0,
},
current_offset: 0,
}
}
fn peek(&mut self) -> Option<char> {
self.chars.peek().copied()
}
fn advance(&mut self) -> Option<char> {
let ch = self.chars.next()?;
self.current_offset += ch.len_utf8();
if ch == '\n' {
self.position.line += 1;
self.position.column = 1;
} else {
self.position.column += 1;
}
self.position.offset = self.current_offset;
Some(ch)
}
fn skip_whitespace(&mut self) {
while let Some(ch) = self.peek() {
if ch.is_whitespace() {
self.advance();
} else if ch == '/' && self.lookahead(1) == Some('/') {
// Skip line comments
while let Some(c) = self.peek() {
if c == '\n' {
break;
}
self.advance();
}
} else {
break;
}
}
}
fn lookahead(&self, n: usize) -> Option<char> {
self.input[self.current_offset..].chars().nth(n)
}
fn make_token(&self, kind: TokenKind, lexeme: &str, start_pos: Position) -> Token {
Token {
kind,
lexeme: lexeme.to_string(),
position: start_pos,
}
}
fn scan_string(&mut self, quote: char) -> Result<Token, LexerError> {
let start = self.position;
self.advance(); // consume opening quote
let mut value = String::new();
while let Some(ch) = self.peek() {
if ch == quote {
self.advance(); // consume closing quote
return Ok(self.make_token(TokenKind::String(value.clone()), &value, start));
} else if ch == '\\' {
self.advance();
match self.peek() {
Some('n') => {
value.push('\n');
self.advance();
}
Some('t') => {
value.push('\t');
self.advance();
}
Some('r') => {
value.push('\r');
self.advance();
}
Some('\\') => {
value.push('\\');
self.advance();
}
Some(c) if c == quote => {
value.push(c);
self.advance();
}
_ => value.push('\\'),
}
} else {
value.push(ch);
self.advance();
}
}
Err(LexerError {
message: "Unterminated string".to_string(),
position: start,
})
}
fn scan_number(&mut self) -> Token {
let start = self.position;
let start_offset = self.current_offset;
while let Some(ch) = self.peek() {
if ch.is_ascii_digit() {
self.advance();
} else {
break;
}
}
// Check for decimal
if self.peek() == Some('.')
&& self
.lookahead(1)
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
self.advance(); // consume '.'
while let Some(ch) = self.peek() {
if ch.is_ascii_digit() {
self.advance();
} else {
break;
}
}
let lexeme = &self.input[start_offset..self.current_offset];
let value: f64 = lexeme.parse().unwrap_or(0.0);
return self.make_token(TokenKind::Float(value), lexeme, start);
}
// Check for exponent
if matches!(self.peek(), Some('e') | Some('E')) {
self.advance();
if matches!(self.peek(), Some('+') | Some('-')) {
self.advance();
}
while let Some(ch) = self.peek() {
if ch.is_ascii_digit() {
self.advance();
} else {
break;
}
}
let lexeme = &self.input[start_offset..self.current_offset];
let value: f64 = lexeme.parse().unwrap_or(0.0);
return self.make_token(TokenKind::Float(value), lexeme, start);
}
let lexeme = &self.input[start_offset..self.current_offset];
let value: i64 = lexeme.parse().unwrap_or(0);
self.make_token(TokenKind::Integer(value), lexeme, start)
}
fn scan_identifier(&mut self) -> Token {
let start = self.position;
let start_offset = self.current_offset;
while let Some(ch) = self.peek() {
if ch.is_ascii_alphanumeric() || ch == '_' {
self.advance();
} else {
break;
}
}
let lexeme = &self.input[start_offset..self.current_offset];
let kind = match lexeme.to_uppercase().as_str() {
"MATCH" => TokenKind::Match,
"OPTIONAL" if self.peek_keyword("MATCH") => {
self.skip_whitespace();
self.scan_keyword("MATCH");
TokenKind::OptionalMatch
}
"WHERE" => TokenKind::Where,
"RETURN" => TokenKind::Return,
"CREATE" => TokenKind::Create,
"MERGE" => TokenKind::Merge,
"DELETE" => TokenKind::Delete,
"DETACH" if self.peek_keyword("DELETE") => {
self.skip_whitespace();
self.scan_keyword("DELETE");
TokenKind::DetachDelete
}
"SET" => TokenKind::Set,
"REMOVE" => TokenKind::Remove,
"WITH" => TokenKind::With,
"ORDER" if self.peek_keyword("BY") => {
self.skip_whitespace();
self.scan_keyword("BY");
TokenKind::OrderBy
}
"LIMIT" => TokenKind::Limit,
"SKIP" => TokenKind::Skip,
"DISTINCT" => TokenKind::Distinct,
"AS" => TokenKind::As,
"ASC" => TokenKind::Asc,
"DESC" => TokenKind::Desc,
"CASE" => TokenKind::Case,
"WHEN" => TokenKind::When,
"THEN" => TokenKind::Then,
"ELSE" => TokenKind::Else,
"END" => TokenKind::End,
"AND" => TokenKind::And,
"OR" => TokenKind::Or,
"XOR" => TokenKind::Xor,
"NOT" => TokenKind::Not,
"IN" => TokenKind::In,
"IS" => TokenKind::Is,
"NULL" => TokenKind::Null,
"TRUE" => TokenKind::True,
"FALSE" => TokenKind::False,
"ON" if self.peek_keyword("CREATE") => {
self.skip_whitespace();
self.scan_keyword("CREATE");
TokenKind::OnCreate
}
_ if lexeme.to_uppercase() == "ON" && self.peek_keyword("MATCH") => {
self.skip_whitespace();
self.scan_keyword("MATCH");
TokenKind::OnMatch
}
_ => TokenKind::Identifier(lexeme.to_string()),
};
self.make_token(kind, lexeme, start)
}
fn peek_keyword(&mut self, keyword: &str) -> bool {
let saved_offset = self.current_offset;
self.skip_whitespace();
let remaining = &self.input[self.current_offset..];
let matches = remaining.to_uppercase().starts_with(keyword)
&& remaining
.chars()
.nth(keyword.len())
.map(|c| !c.is_ascii_alphanumeric() && c != '_')
.unwrap_or(true);
// Reset position if not consuming
if !matches {
self.current_offset = saved_offset;
self.chars = self.input[saved_offset..].chars().peekable();
}
matches
}
fn scan_keyword(&mut self, keyword: &str) {
for _ in 0..keyword.len() {
self.advance();
}
}
pub fn next_token(&mut self) -> Result<Token, LexerError> {
self.skip_whitespace();
let start = self.position;
match self.peek() {
None => Ok(self.make_token(TokenKind::Eof, "", start)),
Some(ch) => {
match ch {
// Strings
'"' | '\'' => self.scan_string(ch),
// Numbers
'0'..='9' => Ok(self.scan_number()),
// Identifiers
'a'..='z' | 'A'..='Z' | '_' | '$' => Ok(self.scan_identifier()),
// Backtick-quoted identifiers
'`' => {
self.advance();
let id_start = self.current_offset;
while let Some(c) = self.peek() {
if c == '`' {
break;
}
self.advance();
}
let id = self.input[id_start..self.current_offset].to_string();
self.advance(); // consume closing backtick
Ok(self.make_token(TokenKind::Identifier(id.clone()), &id, start))
}
// Two-character operators
'<' => {
self.advance();
match self.peek() {
Some('=') => {
self.advance();
Ok(self.make_token(TokenKind::LessThanOrEqual, "<=", start))
}
Some('>') => {
self.advance();
Ok(self.make_token(TokenKind::NotEqual, "<>", start))
}
Some('-') => {
self.advance();
Ok(self.make_token(TokenKind::LeftArrow, "<-", start))
}
_ => Ok(self.make_token(TokenKind::LessThan, "<", start)),
}
}
'>' => {
self.advance();
if self.peek() == Some('=') {
self.advance();
Ok(self.make_token(TokenKind::GreaterThanOrEqual, ">=", start))
} else {
Ok(self.make_token(TokenKind::GreaterThan, ">", start))
}
}
'-' => {
self.advance();
if self.peek() == Some('>') {
self.advance();
Ok(self.make_token(TokenKind::Arrow, "->", start))
} else {
Ok(self.make_token(TokenKind::Dash, "-", start))
}
}
'.' => {
self.advance();
if self.peek() == Some('.') {
self.advance();
Ok(self.make_token(TokenKind::DotDot, "..", start))
} else {
Ok(self.make_token(TokenKind::Dot, ".", start))
}
}
'=' => {
self.advance();
Ok(self.make_token(TokenKind::Equal, "=", start))
}
// Single-character tokens
'(' => {
self.advance();
Ok(self.make_token(TokenKind::LeftParen, "(", start))
}
')' => {
self.advance();
Ok(self.make_token(TokenKind::RightParen, ")", start))
}
'[' => {
self.advance();
Ok(self.make_token(TokenKind::LeftBracket, "[", start))
}
']' => {
self.advance();
Ok(self.make_token(TokenKind::RightBracket, "]", start))
}
'{' => {
self.advance();
Ok(self.make_token(TokenKind::LeftBrace, "{", start))
}
'}' => {
self.advance();
Ok(self.make_token(TokenKind::RightBrace, "}", start))
}
',' => {
self.advance();
Ok(self.make_token(TokenKind::Comma, ",", start))
}
':' => {
self.advance();
Ok(self.make_token(TokenKind::Colon, ":", start))
}
';' => {
self.advance();
Ok(self.make_token(TokenKind::Semicolon, ";", start))
}
'|' => {
self.advance();
Ok(self.make_token(TokenKind::Pipe, "|", start))
}
'+' => {
self.advance();
Ok(self.make_token(TokenKind::Plus, "+", start))
}
'*' => {
self.advance();
Ok(self.make_token(TokenKind::Star, "*", start))
}
'/' => {
self.advance();
Ok(self.make_token(TokenKind::Slash, "/", start))
}
'%' => {
self.advance();
Ok(self.make_token(TokenKind::Percent, "%", start))
}
'^' => {
self.advance();
Ok(self.make_token(TokenKind::Caret, "^", start))
}
_ => Err(LexerError {
message: format!("Unexpected character: '{}'", ch),
position: start,
}),
}
}
}
}
}
/// Tokenize a Cypher query string
pub fn tokenize(input: &str) -> Result<Vec<Token>, LexerError> {
let mut lexer = Lexer::new(input);
let mut tokens = Vec::new();
loop {
let token = lexer.next_token()?;
let is_eof = token.kind == TokenKind::Eof;
tokens.push(token);
if is_eof {
break;
}
}
Ok(tokens)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_tokens() {
let tokens = tokenize("MATCH (n) RETURN n").unwrap();
assert_eq!(tokens[0].kind, TokenKind::Match);
assert_eq!(tokens[1].kind, TokenKind::LeftParen);
}
#[test]
fn test_string() {
let tokens = tokenize("'hello world'").unwrap();
assert_eq!(tokens[0].kind, TokenKind::String("hello world".to_string()));
}
#[test]
fn test_number() {
let tokens = tokenize("42 3.14").unwrap();
assert_eq!(tokens[0].kind, TokenKind::Integer(42));
assert_eq!(tokens[1].kind, TokenKind::Float(3.14));
}
#[test]
fn test_relationship() {
let tokens = tokenize("(a)-[:KNOWS]->(b)").unwrap();
assert!(tokens.iter().any(|t| t.kind == TokenKind::Arrow));
}
}

View File

@@ -0,0 +1,266 @@
//! Cypher query language parser and execution engine for WASM
//!
//! This module provides a WASM-compatible Cypher implementation including:
//! - Lexical analysis (tokenization)
//! - Syntax parsing (AST generation)
//! - In-memory property graph storage
//! - Query execution engine
//!
//! Supported operations:
//! - CREATE: Create nodes and relationships
//! - MATCH: Pattern matching
//! - WHERE: Filtering
//! - RETURN: Projection
//! - SET: Update properties
//! - DELETE/DETACH DELETE: Remove nodes and edges
pub mod ast;
pub mod executor;
pub mod graph_store;
pub mod lexer;
pub mod parser;
pub use ast::{Expression, Pattern, Query, Statement};
pub use executor::{ContextValue, ExecutionError, ExecutionResult, Executor};
pub use graph_store::{Edge, EdgeId, Node, NodeId, PropertyGraph, Value};
pub use lexer::{tokenize, Token, TokenKind};
pub use parser::{parse_cypher, ParseError};
use crate::storage::state::{EdgeState, GraphState, NodeState, PropertyValue};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
/// WASM-compatible Cypher engine
#[wasm_bindgen]
pub struct CypherEngine {
graph: PropertyGraph,
}
#[wasm_bindgen]
impl CypherEngine {
/// Create a new Cypher engine with empty graph
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
graph: PropertyGraph::new(),
}
}
/// Execute a Cypher query and return JSON results
pub fn execute(&mut self, query: &str) -> Result<JsValue, JsValue> {
// Parse the query
let ast =
parse_cypher(query).map_err(|e| JsValue::from_str(&format!("Parse error: {}", e)))?;
// Execute the query
let mut executor = Executor::new(&mut self.graph);
let result = executor
.execute(&ast)
.map_err(|e| JsValue::from_str(&format!("Execution error: {}", e)))?;
// Convert to JS value
serde_wasm_bindgen::to_value(&result)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}
/// Get graph statistics
pub fn stats(&self) -> Result<JsValue, JsValue> {
let stats = self.graph.stats();
serde_wasm_bindgen::to_value(&stats)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}
/// Clear the graph
pub fn clear(&mut self) {
self.graph = PropertyGraph::new();
}
}
impl CypherEngine {
/// Export graph state for persistence
pub fn export_state(&self) -> GraphState {
let nodes: Vec<NodeState> = self
.graph
.all_nodes()
.into_iter()
.map(|n| NodeState {
id: n.id.clone(),
labels: n.labels.clone(),
properties: n
.properties
.iter()
.map(|(k, v)| (k.clone(), value_to_property(v)))
.collect(),
})
.collect();
let edges: Vec<EdgeState> = self
.graph
.all_edges()
.into_iter()
.map(|e| EdgeState {
id: e.id.clone(),
from: e.from.clone(),
to: e.to.clone(),
edge_type: e.edge_type.clone(),
properties: e
.properties
.iter()
.map(|(k, v)| (k.clone(), value_to_property(v)))
.collect(),
})
.collect();
let stats = self.graph.stats();
GraphState {
nodes,
edges,
next_node_id: stats.node_count,
next_edge_id: stats.edge_count,
}
}
/// Import state to restore the graph
pub fn import_state(&mut self, state: &GraphState) -> Result<(), JsValue> {
self.graph = PropertyGraph::new();
// Import nodes first
for node_state in &state.nodes {
let mut node = Node::new(node_state.id.clone());
for label in &node_state.labels {
node = node.with_label(label.clone());
}
for (key, value) in &node_state.properties {
node = node.with_property(key.clone(), property_to_value(value));
}
self.graph.add_node(node);
}
// Then import edges
for edge_state in &state.edges {
let mut edge = Edge::new(
edge_state.id.clone(),
edge_state.from.clone(),
edge_state.to.clone(),
edge_state.edge_type.clone(),
);
for (key, value) in &edge_state.properties {
edge = edge.with_property(key.clone(), property_to_value(value));
}
if let Err(e) = self.graph.add_edge(edge) {
return Err(JsValue::from_str(&format!("Failed to import edge: {}", e)));
}
}
Ok(())
}
}
impl Default for CypherEngine {
fn default() -> Self {
Self::new()
}
}
/// Convert graph Value to serializable PropertyValue
fn value_to_property(v: &Value) -> PropertyValue {
match v {
Value::Null => PropertyValue::Null,
Value::Boolean(b) => PropertyValue::Boolean(*b),
Value::Integer(i) => PropertyValue::Integer(*i),
Value::Float(f) => PropertyValue::Float(*f),
Value::String(s) => PropertyValue::String(s.clone()),
Value::List(list) => PropertyValue::List(list.iter().map(value_to_property).collect()),
Value::Map(map) => PropertyValue::Map(
map.iter()
.map(|(k, v)| (k.clone(), value_to_property(v)))
.collect(),
),
}
}
/// Convert PropertyValue back to graph Value
fn property_to_value(p: &PropertyValue) -> Value {
match p {
PropertyValue::Null => Value::Null,
PropertyValue::Boolean(b) => Value::Boolean(*b),
PropertyValue::Integer(i) => Value::Integer(*i),
PropertyValue::Float(f) => Value::Float(*f),
PropertyValue::String(s) => Value::String(s.clone()),
PropertyValue::List(list) => Value::List(list.iter().map(property_to_value).collect()),
PropertyValue::Map(map) => Value::Map(
map.iter()
.map(|(k, v)| (k.clone(), property_to_value(v)))
.collect(),
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_node() {
let mut engine = CypherEngine::new();
let query = "CREATE (n:Person {name: 'Alice', age: 30})";
let ast = parse_cypher(query).unwrap();
let mut executor = Executor::new(&mut engine.graph);
let result = executor.execute(&ast);
assert!(result.is_ok());
assert_eq!(engine.graph.stats().node_count, 1);
}
#[test]
fn test_create_relationship() {
let mut engine = CypherEngine::new();
let query = "CREATE (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})";
let ast = parse_cypher(query).unwrap();
let mut executor = Executor::new(&mut engine.graph);
let result = executor.execute(&ast);
assert!(result.is_ok());
let stats = engine.graph.stats();
assert_eq!(stats.node_count, 2);
assert_eq!(stats.edge_count, 1);
}
#[test]
fn test_match_nodes() {
let mut engine = CypherEngine::new();
// Create data
let create = "CREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})";
let ast = parse_cypher(create).unwrap();
let mut executor = Executor::new(&mut engine.graph);
executor.execute(&ast).unwrap();
// Match nodes
let match_query = "MATCH (n:Person) RETURN n";
let ast = parse_cypher(match_query).unwrap();
let mut executor = Executor::new(&mut engine.graph);
let result = executor.execute(&ast);
assert!(result.is_ok());
}
#[test]
fn test_parser() {
let queries = vec![
"MATCH (n:Person) RETURN n",
"CREATE (n:Person {name: 'Alice'})",
"MATCH (a)-[r:KNOWS]->(b) RETURN a, r, b",
"CREATE (a:Person)-[r:KNOWS]->(b:Person)",
];
for query in queries {
let result = parse_cypher(query);
assert!(result.is_ok(), "Failed to parse: {}", query);
}
}
}

File diff suppressed because it is too large Load Diff