pub mod cursor;
pub mod cursor_store;
pub mod cypher;
pub mod distributed;
use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::time::Duration;
use std::{collections::HashMap, net::SocketAddr};
pub use cursor::{CursorError, CursorId, CursorResultType, CursorState};
pub use cursor_store::{CursorStore, CursorStoreConfig};
pub use distributed::{
DistributedQueryConfig, MergeStrategy, QueryPlan, QueryPlanner, ResultMerger, ShardId,
ShardResult,
};
use graph_engine::{
CentralityConfig, CommunityConfig, Constraint, ConstraintTarget as GConstraintTarget,
ConstraintType as GConstraintType, Direction, EdgeInput, GraphEngine, GraphError, NodeInput,
PageRankConfig, PropertyValue,
};
use neumann_parser::{
self as parser, error::ParseErrorKind, AggregateFunction, BinaryOp, BlobOp, BlobOptions,
BlobStmt, BlobsOp, BlobsStmt, CacheOp, CacheStmt, ChainOp, ChainStmt, CheckpointStmt,
CheckpointsStmt, ClusterOp, ClusterStmt, ConstraintTarget, ConstraintType, DeleteStmt,
DescribeStmt, DescribeTarget, Direction as ParsedDirection,
DistanceMetric as ParsedDistanceMetric, EdgeOp, EdgeStmt, EmbedOp, EmbedStmt, EntityOp,
EntityStmt, Expr, ExprKind, FindPattern, FindStmt, GraphAggregateOp, GraphAggregateStmt,
GraphAlgorithmOp, GraphAlgorithmStmt, GraphBatchOp, GraphBatchStmt, GraphConstraintOp,
GraphConstraintStmt, GraphIndexOp, GraphIndexStmt, GraphPatternOp, GraphPatternStmt,
InsertSource, InsertStmt, JoinCondition, JoinKind, Literal, NeighborsStmt, NodeOp, NodeStmt,
NullsOrder, PathStmt, Property, RollbackStmt, SelectStmt, SimilarQuery, SimilarStmt,
SortDirection, SpatialOp, SpatialStmt, Statement, StatementKind, TableRefKind, UpdateStmt,
VaultOp, VaultStmt,
};
use relational_engine::{
ColumnarScanOptions, Condition, RelationalEngine, RelationalError, Row, Value,
};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tensor_blob::{BlobConfig, BlobError, BlobStore};
use tensor_cache::{Cache, CacheConfig, CacheError, CacheLayer};
use tensor_chain::{
ChainError, ClusterNodeConfig, ClusterOrchestrator, ClusterPeerConfig, OrchestratorConfig,
QueryExecutor, TensorChain,
};
use tensor_checkpoint::{
CheckpointConfig, CheckpointError, CheckpointManager, ConfirmationHandler, DestructiveOp,
FileCheckpointStore,
};
use tensor_store::{ConsistentHashConfig, ConsistentHashPartitioner, TensorStore};
use tensor_unified::{
FindPattern as UnifiedFindPattern, UnifiedEngine, UnifiedError, UnifiedItem,
UnifiedResult as TensorUnifiedResult,
};
use tensor_vault::{Vault, VaultConfig, VaultError};
use tokio::runtime::Runtime;
use tracing::instrument;
use vector_engine::{DistanceMetric as VectorDistanceMetric, HNSWIndex, VectorEngine, VectorError};
pub use vector_engine::{FilterCondition, FilterStrategy, FilterValue, FilteredSearchConfig};
#[derive(Debug, Clone)]
enum AggregateFunc {
Count(Option<String>), Sum(String),
Avg(String),
Min(String),
Max(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ProtectedOpResult {
Proceed,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RouterError {
ParseError(String),
UnknownCommand(String),
RelationalError(String),
GraphError(String),
VectorError(String),
VaultError(String),
CacheError(String),
BlobError(String),
CheckpointError(String),
ChainError(String),
InvalidArgument(String),
MissingArgument(String),
TypeMismatch(String),
AuthenticationRequired,
NotFound(String),
CursorError(String),
}
impl std::fmt::Display for RouterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
Self::UnknownCommand(cmd) => write!(f, "Unknown command: {cmd}"),
Self::RelationalError(msg) => write!(f, "Relational error: {msg}"),
Self::GraphError(msg) => write!(f, "Graph error: {msg}"),
Self::VectorError(msg) => write!(f, "Vector error: {msg}"),
Self::VaultError(msg) => write!(f, "Vault error: {msg}"),
Self::CacheError(msg) => write!(f, "Cache error: {msg}"),
Self::BlobError(msg) => write!(f, "Blob error: {msg}"),
Self::CheckpointError(msg) => write!(f, "Checkpoint error: {msg}"),
Self::ChainError(msg) => write!(f, "Chain error: {msg}"),
Self::InvalidArgument(msg) => write!(f, "Invalid argument: {msg}"),
Self::TypeMismatch(msg) => write!(f, "Type mismatch: {msg}"),
Self::MissingArgument(msg) => write!(f, "Missing argument: {msg}"),
Self::AuthenticationRequired => {
write!(
f,
"Authentication required: call SET IDENTITY before vault operations"
)
},
Self::NotFound(msg) => write!(f, "Not found: {msg}"),
Self::CursorError(msg) => write!(f, "Cursor error: {msg}"),
}
}
}
impl std::error::Error for RouterError {}
impl From<CursorError> for RouterError {
fn from(e: CursorError) -> Self {
Self::CursorError(e.to_string())
}
}
impl From<RelationalError> for RouterError {
fn from(e: RelationalError) -> Self {
Self::RelationalError(e.to_string())
}
}
impl From<GraphError> for RouterError {
fn from(e: GraphError) -> Self {
Self::GraphError(e.to_string())
}
}
impl From<VectorError> for RouterError {
fn from(e: VectorError) -> Self {
Self::VectorError(e.to_string())
}
}
impl From<VaultError> for RouterError {
fn from(e: VaultError) -> Self {
Self::VaultError(e.to_string())
}
}
impl From<CacheError> for RouterError {
fn from(e: CacheError) -> Self {
Self::CacheError(e.to_string())
}
}
impl From<BlobError> for RouterError {
fn from(e: BlobError) -> Self {
Self::BlobError(e.to_string())
}
}
impl From<CheckpointError> for RouterError {
fn from(e: CheckpointError) -> Self {
Self::CheckpointError(e.to_string())
}
}
impl From<ChainError> for RouterError {
fn from(e: ChainError) -> Self {
Self::ChainError(e.to_string())
}
}
impl From<UnifiedError> for RouterError {
fn from(e: UnifiedError) -> Self {
match e {
UnifiedError::RelationalError(msg) => Self::RelationalError(msg),
UnifiedError::GraphError(msg) => Self::GraphError(msg),
UnifiedError::VectorError(msg) => Self::VectorError(msg),
UnifiedError::NotFound(msg) => Self::VectorError(format!("Not found: {msg}")),
UnifiedError::InvalidOperation(msg) => Self::InvalidArgument(msg),
UnifiedError::BatchOperationFailed { index, key, cause } => Self::VectorError(format!(
"Batch operation failed at index {index} (key: {key}): {cause}"
)),
UnifiedError::SpatialError(msg) => {
Self::InvalidArgument(format!("Spatial error: {msg}"))
},
}
}
}
pub type Result<T> = std::result::Result<T, RouterError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum QueryResult {
Empty,
Value(String),
Count(usize),
Ids(Vec<u64>),
Rows(Vec<Row>),
Nodes(Vec<NodeResult>),
Edges(Vec<EdgeResult>),
Path(Vec<u64>),
Similar(Vec<SimilarResult>),
Unified(UnifiedResult),
TableList(Vec<String>),
Blob(Vec<u8>),
ArtifactInfo(ArtifactInfoResult),
ArtifactList(Vec<String>),
BlobStats(BlobStatsResult),
CheckpointList(Vec<CheckpointInfo>),
Chain(ChainResult),
PageRank(PageRankResult),
Centrality(CentralityResult),
Communities(CommunityResult),
Constraints(Vec<ConstraintInfo>),
GraphIndexes(Vec<String>),
Aggregate(AggregateResultValue),
BatchResult(BatchOperationResult),
PatternMatch(PatternMatchResultValue),
Spatial(Vec<SpatialResult>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PagedQueryResult {
pub result: QueryResult,
pub next_cursor: Option<String>,
pub prev_cursor: Option<String>,
pub total_count: Option<usize>,
pub has_more: bool,
pub page_size: usize,
}
#[derive(Debug, Clone, Default)]
pub struct PaginationOptions {
pub cursor: Option<String>,
pub page_size: Option<usize>,
pub count_total: bool,
pub cursor_ttl: Option<Duration>,
}
impl PaginationOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_cursor(mut self, cursor: String) -> Self {
self.cursor = Some(cursor);
self
}
#[must_use]
pub const fn with_page_size(mut self, size: usize) -> Self {
self.page_size = Some(size);
self
}
#[must_use]
pub const fn with_count_total(mut self, count: bool) -> Self {
self.count_total = count;
self
}
#[must_use]
pub const fn with_cursor_ttl(mut self, ttl: Duration) -> Self {
self.cursor_ttl = Some(ttl);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeResult {
pub id: u64,
pub label: String,
pub properties: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeResult {
pub id: u64,
pub from: u64,
pub to: u64,
pub label: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimilarResult {
pub key: String,
pub score: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpatialResult {
pub key: String,
pub distance: f32,
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedResult {
pub description: String,
pub items: Vec<UnifiedItem>,
}
impl From<TensorUnifiedResult> for UnifiedResult {
fn from(r: TensorUnifiedResult) -> Self {
Self {
description: r.description,
items: r.items,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactInfoResult {
pub id: String,
pub filename: String,
pub content_type: String,
pub size: usize,
pub checksum: String,
pub chunk_count: usize,
pub created: u64,
pub modified: u64,
pub created_by: String,
pub tags: Vec<String>,
pub linked_to: Vec<String>,
pub custom: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlobStatsResult {
pub artifact_count: usize,
pub chunk_count: usize,
pub total_bytes: usize,
pub unique_bytes: usize,
pub dedup_ratio: f64,
pub orphaned_chunks: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointInfo {
pub id: String,
pub name: String,
pub created_at: u64,
pub is_auto: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChainResult {
TransactionBegun { tx_id: String },
Committed { block_hash: String, height: u64 },
RolledBack { to_height: u64 },
History(Vec<ChainHistoryEntry>),
Similar(Vec<ChainSimilarResult>),
Drift(ChainDriftResult),
Height(u64),
Tip { hash: String, height: u64 },
Block(ChainBlockInfo),
Codebook(ChainCodebookInfo),
Verified { ok: bool, errors: Vec<String> },
TransitionAnalysis(ChainTransitionAnalysis),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainHistoryEntry {
pub height: u64,
pub transaction_type: String,
pub data: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainSimilarResult {
pub block_hash: String,
pub height: u64,
pub similarity: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainDriftResult {
pub from_height: u64,
pub to_height: u64,
pub total_drift: f32,
pub avg_drift_per_block: f32,
pub max_drift: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainBlockInfo {
pub height: u64,
pub hash: String,
pub prev_hash: String,
pub timestamp: u64,
pub transaction_count: usize,
pub proposer: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainCodebookInfo {
pub scope: String,
pub entry_count: usize,
pub dimension: usize,
pub domain: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainTransitionAnalysis {
pub total_transitions: usize,
pub valid_transitions: usize,
pub invalid_transitions: usize,
pub avg_validity_score: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageRankItem {
pub node_id: u64,
pub score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageRankResult {
pub items: Vec<PageRankItem>,
pub iterations: usize,
pub convergence: f64,
pub converged: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CentralityType {
Betweenness,
Closeness,
Eigenvector,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CentralityItem {
pub node_id: u64,
pub score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CentralityResult {
pub items: Vec<CentralityItem>,
pub centrality_type: CentralityType,
pub iterations: Option<usize>,
pub converged: Option<bool>,
pub sample_count: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommunityItem {
pub node_id: u64,
pub community_id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommunityResult {
pub items: Vec<CommunityItem>,
pub members: HashMap<u64, Vec<u64>>,
pub community_count: usize,
pub modularity: Option<f64>,
pub passes: Option<usize>,
pub iterations: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstraintInfo {
pub name: String,
pub target: String,
pub property: String,
pub constraint_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AggregateResultValue {
Count(u64),
Sum(f64),
Avg(f64),
Min(f64),
Max(f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchOperationResult {
pub operation: String,
pub affected_count: usize,
pub created_ids: Option<Vec<u64>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternMatchResultValue {
pub matches: Vec<PatternMatchBinding>,
pub stats: PatternMatchStatsValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternMatchBinding {
pub bindings: HashMap<String, BindingValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BindingValue {
Node {
id: u64,
label: String,
},
Edge {
id: u64,
edge_type: String,
from: u64,
to: u64,
},
Path {
nodes: Vec<u64>,
edges: Vec<u64>,
length: usize,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternMatchStatsValue {
pub matches_found: usize,
pub nodes_evaluated: usize,
pub edges_evaluated: usize,
pub truncated: bool,
}
impl QueryResult {
#[must_use]
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string())
}
#[must_use]
pub fn to_pretty_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
}
#[must_use]
pub const fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
#[must_use]
pub const fn as_count(&self) -> Option<usize> {
if let Self::Count(n) = self {
Some(*n)
} else {
None
}
}
#[must_use]
pub fn as_value(&self) -> Option<&str> {
if let Self::Value(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub fn as_rows(&self) -> Option<&[Row]> {
if let Self::Rows(rows) = self {
Some(rows)
} else {
None
}
}
}
pub struct QueryRouter {
relational: Arc<RelationalEngine>,
graph: Arc<GraphEngine>,
vector: Arc<VectorEngine>,
unified: Option<UnifiedEngine>,
vault: Option<Arc<Vault>>,
cache: Option<Arc<Cache>>,
blob: Option<Arc<tokio::sync::Mutex<BlobStore>>>,
blob_runtime: Option<Arc<Runtime>>,
current_identity: Option<String>,
hnsw_index: Option<(HNSWIndex, Vec<String>)>,
vector_generation: AtomicU64,
hnsw_generation: AtomicU64,
checkpoint_dir: Option<PathBuf>,
checkpoint: Option<Arc<CheckpointManager>>,
chain: Option<Arc<TensorChain>>,
cluster: Option<Arc<ClusterOrchestrator>>,
cluster_runtime: Option<Arc<Runtime>>,
distributed_planner: Option<Arc<QueryPlanner>>,
distributed_config: DistributedQueryConfig,
local_shard_id: ShardId,
cursor_store: Arc<CursorStore>,
spatial: Arc<parking_lot::RwLock<tensor_spatial::SpatialIndex<String>>>,
}
impl QueryRouter {
#[must_use]
pub fn new() -> Self {
Self::with_shared_store(TensorStore::new())
}
pub fn with_engines(
relational: Arc<RelationalEngine>,
graph: Arc<GraphEngine>,
vector: Arc<VectorEngine>,
) -> Self {
let store = vector.store().clone();
let unified = UnifiedEngine::with_engines(
store,
Arc::clone(&relational),
Arc::clone(&graph),
Arc::clone(&vector),
);
Self {
relational,
graph,
vector,
unified: Some(unified),
vault: None,
cache: None,
blob: None,
blob_runtime: None,
current_identity: None,
hnsw_index: None,
vector_generation: AtomicU64::new(0),
hnsw_generation: AtomicU64::new(0),
checkpoint_dir: None,
checkpoint: None,
chain: None,
cluster: None,
cluster_runtime: None,
distributed_planner: None,
distributed_config: DistributedQueryConfig::default(),
local_shard_id: 0,
cursor_store: Arc::new(CursorStore::new()),
spatial: Arc::new(parking_lot::RwLock::new(tensor_spatial::SpatialIndex::new())),
}
}
#[must_use]
pub fn with_shared_store(store: TensorStore) -> Self {
let relational = Arc::new(RelationalEngine::with_store(store.clone()));
let graph = Arc::new(GraphEngine::with_store(store.clone()));
let vector = Arc::new(VectorEngine::with_store(store.clone()));
let unified = UnifiedEngine::with_engines(
store,
Arc::clone(&relational),
Arc::clone(&graph),
Arc::clone(&vector),
);
Self {
relational,
graph,
vector,
unified: Some(unified),
vault: None,
cache: None,
blob: None,
blob_runtime: None,
current_identity: None,
hnsw_index: None,
vector_generation: AtomicU64::new(0),
hnsw_generation: AtomicU64::new(0),
checkpoint_dir: None,
checkpoint: None,
chain: None,
cluster: None,
cluster_runtime: None,
distributed_planner: None,
distributed_config: DistributedQueryConfig::default(),
local_shard_id: 0,
cursor_store: Arc::new(CursorStore::new()),
spatial: Arc::new(parking_lot::RwLock::new(tensor_spatial::SpatialIndex::new())),
}
}
pub fn relational(&self) -> &RelationalEngine {
&self.relational
}
pub fn graph(&self) -> &GraphEngine {
&self.graph
}
pub fn vector(&self) -> &VectorEngine {
&self.vector
}
pub const fn spatial(&self) -> &Arc<parking_lot::RwLock<tensor_spatial::SpatialIndex<String>>> {
&self.spatial
}
pub const fn unified(&self) -> Option<&UnifiedEngine> {
self.unified.as_ref()
}
fn require_unified(&self) -> Result<&UnifiedEngine> {
self.unified.as_ref().ok_or_else(|| {
RouterError::InvalidArgument("Unified engine not initialized".to_string())
})
}
fn create_runtime() -> Result<Runtime> {
Runtime::new()
.map_err(|e| RouterError::InvalidArgument(format!("Failed to create runtime: {e}")))
}
pub fn vault(&self) -> Option<&Vault> {
self.vault.as_deref()
}
pub fn init_vault(&mut self, master_key: &[u8]) -> Result<()> {
let vault = Vault::new(
master_key,
Arc::clone(&self.graph),
self.vector.store().clone(),
VaultConfig::default(),
)?;
self.vault = Some(Arc::new(vault));
Ok(())
}
pub fn cache(&self) -> Option<&Cache> {
self.cache.as_deref()
}
pub fn init_cache(&mut self) {
self.cache = Some(Arc::new(Cache::new()));
}
pub fn init_cache_default(&mut self) -> Result<()> {
self.cache = Some(Arc::new(Cache::new()));
Ok(())
}
pub fn init_cache_with_config(&mut self, config: CacheConfig) -> Result<()> {
let cache =
Cache::with_config(config).map_err(|e| RouterError::CacheError(e.to_string()))?;
self.cache = Some(Arc::new(cache));
Ok(())
}
pub const fn blob(&self) -> Option<&Arc<tokio::sync::Mutex<BlobStore>>> {
self.blob.as_ref()
}
pub fn ensure_vault(&mut self) -> Result<&Vault> {
if self.vault.is_none() {
if let Ok(key) = std::env::var("NEUMANN_VAULT_KEY") {
self.init_vault(key.as_bytes())?;
} else {
return Err(RouterError::VaultError(
"Vault not initialized. Set NEUMANN_VAULT_KEY env var or call init_vault()"
.to_string(),
));
}
}
Ok(self.vault.as_deref().unwrap())
}
pub fn ensure_cache(&mut self) -> &Cache {
if self.cache.is_none() {
self.init_cache();
}
self.cache.as_deref().unwrap()
}
pub fn ensure_blob(&mut self) -> Result<&Arc<tokio::sync::Mutex<BlobStore>>> {
if self.blob.is_none() {
self.init_blob()?;
}
Ok(self.blob.as_ref().unwrap())
}
pub fn init_blob(&mut self) -> Result<()> {
self.init_blob_with_config(BlobConfig::default())
}
pub fn init_blob_with_config(&mut self, config: BlobConfig) -> Result<()> {
let runtime = Runtime::new()
.map_err(|e| RouterError::BlobError(format!("Failed to create runtime: {e}")))?;
let store = self.vector.store().clone();
let blob_store = runtime
.block_on(async { BlobStore::new(store, config).await })
.map_err(|e| RouterError::BlobError(e.to_string()))?;
self.blob = Some(Arc::new(tokio::sync::Mutex::new(blob_store)));
self.blob_runtime = Some(Arc::new(runtime));
Ok(())
}
pub fn start_blob(&mut self) -> Result<()> {
let blob = self
.blob
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob store not initialized".to_string()))?;
let runtime = self
.blob_runtime
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob runtime not initialized".to_string()))?;
runtime.block_on(async {
let mut blob_guard = blob.lock().await;
blob_guard.start().await
})?;
Ok(())
}
pub fn shutdown_blob(&mut self) -> Result<()> {
if let (Some(blob), Some(runtime)) = (self.blob.as_ref(), self.blob_runtime.as_ref()) {
runtime.block_on(async {
let mut blob_guard = blob.lock().await;
blob_guard.shutdown().await
})?;
}
Ok(())
}
pub fn init_cluster(
&mut self,
node_id: &str,
bind_addr: SocketAddr,
peers: &[(String, SocketAddr)],
) -> Result<()> {
self.init_cluster_with_executor(node_id, bind_addr, peers, None)
}
pub fn init_cluster_with_wal(
&mut self,
node_id: &str,
bind_addr: SocketAddr,
peers: &[(String, SocketAddr)],
wal_dir: &std::path::Path,
) -> Result<()> {
if self.cluster.is_some() {
return Err(RouterError::InvalidArgument(
"Cluster already initialized".to_string(),
));
}
let runtime = Runtime::new()
.map_err(|e| RouterError::ChainError(format!("Failed to create runtime: {e}")))?;
let local_config = ClusterNodeConfig::new(node_id, bind_addr);
let peer_configs: Vec<ClusterPeerConfig> = peers
.iter()
.map(|(id, addr)| ClusterPeerConfig::new(id.clone(), *addr))
.collect();
let config =
OrchestratorConfig::new(local_config, peer_configs).with_wal_dir(wal_dir.to_path_buf());
let orchestrator = runtime
.block_on(ClusterOrchestrator::start(config))
.map_err(|e| RouterError::ChainError(e.to_string()))?;
let all_nodes: Vec<String> = std::iter::once(node_id.to_string())
.chain(peers.iter().map(|(id, _)| id.clone()))
.collect();
let hash_config = ConsistentHashConfig::new(node_id);
let partitioner = ConsistentHashPartitioner::with_nodes(hash_config, all_nodes.clone());
let local_shard = all_nodes.iter().position(|n| n == node_id).unwrap_or(0);
let planner = QueryPlanner::new(Arc::new(partitioner), local_shard);
self.cluster = Some(Arc::new(orchestrator));
self.cluster_runtime = Some(Arc::new(runtime));
self.distributed_planner = Some(Arc::new(planner));
self.local_shard_id = local_shard;
Ok(())
}
pub fn init_cluster_with_executor(
&mut self,
node_id: &str,
bind_addr: SocketAddr,
peers: &[(String, SocketAddr)],
executor: Option<Arc<dyn tensor_chain::QueryExecutor>>,
) -> Result<()> {
if self.cluster.is_some() {
return Err(RouterError::InvalidArgument(
"Cluster already initialized".to_string(),
));
}
let runtime = Runtime::new()
.map_err(|e| RouterError::ChainError(format!("Failed to create runtime: {e}")))?;
let local_config = ClusterNodeConfig::new(node_id, bind_addr);
let peer_configs: Vec<ClusterPeerConfig> = peers
.iter()
.map(|(id, addr)| ClusterPeerConfig::new(id.clone(), *addr))
.collect();
let config = OrchestratorConfig::new(local_config, peer_configs);
let orchestrator = runtime
.block_on(ClusterOrchestrator::start(config))
.map_err(|e| RouterError::ChainError(e.to_string()))?;
if let Some(exec) = executor {
orchestrator.register_query_executor(exec);
}
let all_nodes: Vec<String> = std::iter::once(node_id.to_string())
.chain(peers.iter().map(|(id, _)| id.clone()))
.collect();
let hash_config = ConsistentHashConfig::new(node_id);
let partitioner = ConsistentHashPartitioner::with_nodes(hash_config, all_nodes.clone());
let local_shard = all_nodes.iter().position(|n| n == node_id).unwrap_or(0);
let planner = QueryPlanner::new(Arc::new(partitioner), local_shard);
self.cluster = Some(Arc::new(orchestrator));
self.cluster_runtime = Some(Arc::new(runtime));
self.distributed_planner = Some(Arc::new(planner));
self.local_shard_id = local_shard;
Ok(())
}
pub fn shutdown_cluster(&mut self) -> Result<()> {
if let (Some(cluster), Some(runtime)) =
(self.cluster.as_ref(), self.cluster_runtime.as_ref())
{
runtime
.block_on(cluster.shutdown())
.map_err(|e| RouterError::ChainError(e.to_string()))?;
}
self.cluster = None;
self.cluster_runtime = None;
Ok(())
}
pub const fn is_cluster_active(&self) -> bool {
self.cluster.is_some()
}
pub const fn cluster(&self) -> Option<&Arc<ClusterOrchestrator>> {
self.cluster.as_ref()
}
pub const fn checkpoint(&self) -> Option<&Arc<CheckpointManager>> {
self.checkpoint.as_ref()
}
pub fn set_checkpoint_dir(&mut self, dir: PathBuf) {
self.checkpoint_dir = Some(dir);
}
pub fn checkpoint_dir(&self) -> Option<&Path> {
self.checkpoint_dir.as_deref()
}
pub fn init_checkpoint(&mut self) -> Result<()> {
self.init_checkpoint_with_config(CheckpointConfig::default())
}
pub fn init_checkpoint_with_config(&mut self, config: CheckpointConfig) -> Result<()> {
let dir = self.checkpoint_dir.as_ref().ok_or_else(|| {
RouterError::CheckpointError(
"Checkpoint directory must be set before initializing checkpoint manager"
.to_string(),
)
})?;
let store = Arc::new(
FileCheckpointStore::new(dir)
.map_err(|e| RouterError::CheckpointError(e.to_string()))?,
);
let manager = CheckpointManager::new(store, config);
self.checkpoint = Some(Arc::new(manager));
Ok(())
}
pub fn ensure_checkpoint(&mut self) -> Result<&Arc<CheckpointManager>> {
if self.checkpoint.is_none() {
self.init_checkpoint()?;
}
Ok(self.checkpoint.as_ref().unwrap())
}
pub const fn has_checkpoint(&self) -> bool {
self.checkpoint.is_some()
}
pub const fn has_hnsw_index(&self) -> bool {
self.hnsw_index.is_some()
}
fn bump_vector_generation(&self) {
self.vector_generation.fetch_add(1, AtomicOrdering::SeqCst);
}
fn hnsw_is_fresh(&self) -> bool {
self.hnsw_generation.load(AtomicOrdering::SeqCst)
== self.vector_generation.load(AtomicOrdering::SeqCst)
}
pub fn tls_cert_path(&self) -> Option<std::path::PathBuf> {
self.cluster.as_ref().and_then(|c| c.tls_cert_path())
}
pub fn set_confirmation_handler(&self, handler: Arc<dyn ConfirmationHandler>) -> Result<()> {
let checkpoint = self.checkpoint.as_ref().ok_or_else(|| {
RouterError::CheckpointError("Checkpoint manager not initialized".to_string())
})?;
checkpoint.set_confirmation_handler(handler);
Ok(())
}
pub fn init_chain(&mut self, node_id: &str) -> Result<()> {
let store = self.vector.store().clone();
let chain = TensorChain::new(store, node_id);
chain.initialize()?;
self.chain = Some(Arc::new(chain));
Ok(())
}
pub const fn chain(&self) -> Option<&Arc<TensorChain>> {
self.chain.as_ref()
}
pub fn ensure_chain(&mut self) -> Result<&Arc<TensorChain>> {
if self.chain.is_none() {
self.init_chain("default_node")?;
}
Ok(self.chain.as_ref().unwrap())
}
pub fn set_identity(&mut self, identity: &str) {
self.current_identity = Some(identity.to_string());
}
pub fn current_identity(&self) -> Option<&str> {
self.current_identity.as_deref()
}
fn require_identity(&self) -> Result<&str> {
self.current_identity
.as_deref()
.ok_or(RouterError::AuthenticationRequired)
}
pub const fn is_authenticated(&self) -> bool {
self.current_identity.is_some()
}
pub fn build_vector_index(&mut self) -> Result<()> {
let (index, keys) = self.vector.build_hnsw_index_default()?;
self.hnsw_index = Some((index, keys));
self.hnsw_generation.store(
self.vector_generation.load(AtomicOrdering::SeqCst),
AtomicOrdering::SeqCst,
);
Ok(())
}
pub fn find_similar_connected(
&self,
query_key: &str,
connected_to: &str,
top_k: usize,
) -> Result<Vec<UnifiedItem>> {
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
let hnsw_results = if let Some((ref index, ref keys)) =
self.hnsw_index.as_ref().filter(|_| self.hnsw_is_fresh())
{
let query_embedding = self
.vector
.get_entity_embedding(query_key)
.map_err(|e| RouterError::VectorError(e.to_string()))?;
Some(
self.vector
.search_with_hnsw(index, keys, &query_embedding, top_k.saturating_mul(8))
.map_err(|e| RouterError::VectorError(e.to_string()))?,
)
} else {
None
};
runtime
.block_on(unified.find_similar_connected_with_hnsw(
query_key,
connected_to,
top_k,
hnsw_results,
))
.map_err(Into::into)
}
pub fn find_neighbors_by_similarity(
&self,
entity_key: &str,
query: &[f32],
top_k: usize,
) -> Result<Vec<UnifiedItem>> {
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
runtime
.block_on(unified.find_neighbors_by_similarity(entity_key, query, top_k))
.map_err(Into::into)
}
pub fn create_unified_entity(
&self,
key: &str,
fields: HashMap<String, String>,
embedding: Option<Vec<f32>>,
) -> Result<()> {
let has_embedding = embedding.is_some();
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
runtime
.block_on(unified.create_entity(key, fields, embedding))
.map_err(|e| RouterError::VectorError(e.to_string()))?;
if has_embedding {
self.bump_vector_generation();
}
Ok(())
}
pub fn connect_entities(
&self,
from_key: &str,
to_key: &str,
edge_type: &str,
) -> Result<String> {
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
runtime
.block_on(unified.connect_entities(from_key, to_key, edge_type))
.map(|edge_id| format!("edge:{edge_type}:{edge_id}"))
.map_err(|e| RouterError::GraphError(e.to_string()))
}
#[instrument(skip(self))]
#[allow(clippy::too_many_lines)]
pub fn execute(&self, command: &str) -> Result<QueryResult> {
let trimmed = command.trim();
if trimmed.is_empty() {
return Err(RouterError::ParseError("Empty command".to_string()));
}
if let Some(result) = self.try_execute_distributed(trimmed) {
return result;
}
let stmt = parser::parse(trimmed).map_err(|parse_err| {
let upper = trimmed.to_ascii_uppercase();
let first_word = upper.split_whitespace().next().unwrap_or("");
if matches!(&parse_err.kind, ParseErrorKind::UnknownCommand(_)) {
RouterError::UnknownCommand(first_word.to_string())
} else {
RouterError::ParseError(parse_err.format_with_source(trimmed))
}
})?;
if Self::is_cacheable_statement(&stmt) {
if let Some(cached) = self.try_cache_get(trimmed) {
return Ok(cached);
}
}
let result = self.execute_statement(&stmt)?;
if Self::is_cacheable_statement(&stmt) {
self.try_cache_put(trimmed, &result);
}
if Self::is_write_statement(&stmt) {
self.invalidate_cache_on_write();
}
Ok(result)
}
#[instrument(skip(self))]
#[allow(clippy::needless_pass_by_value)] pub fn execute_paginated(
&self,
command: &str,
options: PaginationOptions,
) -> Result<PagedQueryResult> {
let page_size = options.page_size.unwrap_or(CursorState::DEFAULT_PAGE_SIZE);
#[allow(clippy::cast_possible_truncation)] let ttl_secs = options
.cursor_ttl
.map_or(CursorState::DEFAULT_TTL_SECS, |d| d.as_secs() as u32)
.min(CursorState::MAX_TTL_SECS);
let cursor_state = if let Some(ref token) = options.cursor {
let state = CursorState::decode(token)?;
if state.query != command {
return Err(RouterError::CursorError(
"Cursor query does not match request".to_string(),
));
}
Some(state)
} else {
None
};
let full_result = self.execute(command)?;
let (result_type, total_count, paged_result) = self.apply_pagination(
&full_result,
cursor_state.as_ref(),
page_size,
options.count_total,
)?;
let offset = cursor_state.as_ref().map_or(0, |s| s.offset);
let has_more = total_count.is_some_and(|total| offset + page_size < total);
let next_cursor = if has_more {
let cursor_id = uuid::Uuid::new_v4().to_string();
let mut next_state = CursorState::new(
cursor_id,
command.to_string(),
result_type,
page_size,
total_count,
ttl_secs,
);
next_state.offset = offset + page_size;
self.cursor_store.insert(next_state.clone())?;
Some(next_state.encode()?)
} else {
None
};
let prev_cursor = if offset > 0 {
let cursor_id = uuid::Uuid::new_v4().to_string();
let mut prev_state = CursorState::new(
cursor_id,
command.to_string(),
result_type,
page_size,
total_count,
ttl_secs,
);
prev_state.offset = offset.saturating_sub(page_size);
self.cursor_store.insert(prev_state.clone())?;
Some(prev_state.encode()?)
} else {
None
};
Ok(PagedQueryResult {
result: paged_result,
next_cursor,
prev_cursor,
total_count,
has_more,
page_size,
})
}
#[allow(clippy::unused_self)] fn apply_pagination(
&self,
result: &QueryResult,
cursor_state: Option<&CursorState>,
page_size: usize,
count_total: bool,
) -> Result<(CursorResultType, Option<usize>, QueryResult)> {
let offset = cursor_state.map_or(0, |s| s.offset);
match result {
QueryResult::Rows(rows) => {
let total = if count_total { Some(rows.len()) } else { None };
let paged: Vec<_> = rows.iter().skip(offset).take(page_size).cloned().collect();
Ok((CursorResultType::Rows, total, QueryResult::Rows(paged)))
},
QueryResult::Nodes(nodes) => {
let total = if count_total { Some(nodes.len()) } else { None };
let paged: Vec<_> = nodes.iter().skip(offset).take(page_size).cloned().collect();
Ok((CursorResultType::Nodes, total, QueryResult::Nodes(paged)))
},
QueryResult::Edges(edges) => {
let total = if count_total { Some(edges.len()) } else { None };
let paged: Vec<_> = edges.iter().skip(offset).take(page_size).cloned().collect();
Ok((CursorResultType::Edges, total, QueryResult::Edges(paged)))
},
QueryResult::Similar(items) => {
let total = if count_total { Some(items.len()) } else { None };
let paged: Vec<_> = items.iter().skip(offset).take(page_size).cloned().collect();
Ok((
CursorResultType::Similar,
total,
QueryResult::Similar(paged),
))
},
QueryResult::Unified(unified) => {
let total = if count_total {
Some(unified.items.len())
} else {
None
};
let paged_items: Vec<_> = unified
.items
.iter()
.skip(offset)
.take(page_size)
.cloned()
.collect();
Ok((
CursorResultType::Unified,
total,
QueryResult::Unified(UnifiedResult {
description: unified.description.clone(),
items: paged_items,
}),
))
},
QueryResult::PatternMatch(pattern) => {
let total = if count_total {
Some(pattern.matches.len())
} else {
None
};
let paged_matches: Vec<_> = pattern
.matches
.iter()
.skip(offset)
.take(page_size)
.cloned()
.collect();
Ok((
CursorResultType::PatternMatch,
total,
QueryResult::PatternMatch(PatternMatchResultValue {
matches: paged_matches,
stats: pattern.stats.clone(),
}),
))
},
_ => Err(RouterError::InvalidArgument(
"Result type does not support pagination".to_string(),
)),
}
}
pub fn close_cursor(&self, cursor_token: &str) -> Result<bool> {
let state = CursorState::decode(cursor_token)?;
Ok(self.cursor_store.remove(&state.id))
}
#[must_use]
pub const fn cursor_store(&self) -> &Arc<CursorStore> {
&self.cursor_store
}
fn try_execute_distributed(&self, command: &str) -> Option<Result<QueryResult>> {
let planner = self.distributed_planner.as_ref()?;
let cluster = self.cluster.as_ref()?;
let runtime = self.cluster_runtime.as_ref()?;
let plan = planner.plan(command);
match plan {
QueryPlan::Local { .. } => None, QueryPlan::Remote { shard, query } => {
Some(self.execute_on_shard(runtime, cluster, shard, &query))
},
QueryPlan::ScatterGather {
shards,
query,
merge,
} => {
Some(self.execute_scatter_gather(runtime, cluster, &shards, &query, &merge))
},
}
}
fn execute_on_shard(
&self,
runtime: &Runtime,
cluster: &ClusterOrchestrator,
shard: ShardId,
query: &str,
) -> Result<QueryResult> {
let nodes = cluster.membership().view().nodes;
let node_id = nodes
.get(shard)
.map(|n| n.node_id.clone())
.ok_or_else(|| RouterError::InvalidArgument(format!("Shard {shard} not found")))?;
let request = tensor_chain::QueryRequest {
query_id: rand::random(),
query: query.to_string(),
shard_id: shard,
embedding: None,
timeout_ms: self.distributed_config.shard_timeout_ms,
};
let response = runtime.block_on(async {
cluster
.send_query(&node_id, request)
.await
.map_err(|e| RouterError::ChainError(e.to_string()))
})?;
if response.success {
bitcode::deserialize(&response.result)
.map_err(|e| RouterError::ChainError(format!("Deserialization error: {e}")))
} else {
Err(RouterError::ChainError(
response
.error
.unwrap_or_else(|| "Unknown error".to_string()),
))
}
}
fn execute_scatter_gather(
&self,
runtime: &Runtime,
cluster: &ClusterOrchestrator,
shards: &[ShardId],
query: &str,
merge_strategy: &MergeStrategy,
) -> Result<QueryResult> {
let nodes = cluster.membership().view().nodes;
let results: Vec<ShardResult> = runtime.block_on(async {
let mut results = Vec::with_capacity(shards.len());
for &shard in shards {
let start = std::time::Instant::now();
if shard == self.local_shard_id {
match self.execute_parsed_local(query) {
Ok(result) => {
#[allow(clippy::cast_possible_truncation)]
let elapsed = start.elapsed().as_micros() as u64;
results.push(ShardResult::success(shard, result, elapsed));
},
Err(e) => {
if self.distributed_config.fail_fast {
return vec![ShardResult::error(shard, e.to_string())];
}
results.push(ShardResult::error(shard, e.to_string()));
},
}
continue;
}
let Some(n) = nodes.get(shard) else {
results.push(ShardResult::error(
shard,
format!("Shard {shard} not found"),
));
continue;
};
let node_id = n.node_id.clone();
let request = tensor_chain::QueryRequest {
query_id: rand::random(),
query: query.to_string(),
shard_id: shard,
embedding: None,
timeout_ms: self.distributed_config.shard_timeout_ms,
};
match cluster.send_query(&node_id, request).await {
Ok(response) => {
if response.success {
match bitcode::deserialize(&response.result) {
Ok(result) => {
results.push(ShardResult::success(
shard,
result,
response.execution_time_us,
));
},
Err(e) => {
results.push(ShardResult::error(
shard,
format!("Deserialization error: {e}"),
));
},
}
} else {
results.push(ShardResult::error(
shard,
response
.error
.unwrap_or_else(|| "Unknown error".to_string()),
));
}
},
Err(e) => {
results.push(ShardResult::error(shard, e.to_string()));
},
}
}
results
});
ResultMerger::merge(results, merge_strategy)
}
fn execute_parsed_local(&self, command: &str) -> Result<QueryResult> {
let stmt = parser::parse(command)
.map_err(|e| RouterError::ParseError(e.format_with_source(command)))?;
self.execute_statement(&stmt)
}
pub fn execute_parsed(&self, command: &str) -> Result<QueryResult> {
if let Some(result) = self.try_execute_distributed(command) {
return result;
}
let stmt = parser::parse(command)
.map_err(|e| RouterError::ParseError(e.format_with_source(command)))?;
if Self::is_cacheable_statement(&stmt) {
if let Some(cached) = self.try_cache_get(command) {
return Ok(cached);
}
}
let result = self.execute_statement(&stmt)?;
if Self::is_cacheable_statement(&stmt) {
self.try_cache_put(command, &result);
}
if Self::is_write_statement(&stmt) {
self.invalidate_cache_on_write();
}
Ok(result)
}
#[instrument(skip(self, stmt))]
#[allow(clippy::too_many_lines)] pub fn execute_statement(&self, stmt: &Statement) -> Result<QueryResult> {
match &stmt.kind {
StatementKind::Select(select) => self.exec_select(select),
StatementKind::Insert(insert) => self.exec_insert(insert),
StatementKind::Update(update) => self.exec_update(update),
StatementKind::Delete(delete) => self.exec_delete(delete),
StatementKind::CreateTable(create) => self.exec_create_table(create),
StatementKind::DropTable(drop) => {
let table = &drop.table.name;
let (row_count, sample_data) = self.collect_table_sample(table, 5);
let op = DestructiveOp::DropTable {
table: table.clone(),
row_count,
};
match self.protect_destructive_op(&format!("DROP TABLE {table}"), op, sample_data) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
self.relational.drop_table(table)?;
Ok(QueryResult::Empty)
},
StatementKind::CreateIndex(create) => {
if let Some(col) = create.columns.first() {
self.relational
.create_index(&create.table.name, &col.name)?;
}
Ok(QueryResult::Empty)
},
StatementKind::DropIndex(drop) => {
if let (Some(table), Some(column)) = (&drop.table, &drop.column) {
if drop.if_exists && !self.relational.has_index(&table.name, &column.name) {
return Ok(QueryResult::Empty);
}
let op = DestructiveOp::DropIndex {
table: table.name.clone(),
column: column.name.clone(),
};
match self.protect_destructive_op(
&format!("DROP INDEX ON {}({})", table.name, column.name),
op,
vec![format!("index on {}.{}", table.name, column.name)],
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
self.relational.drop_index(&table.name, &column.name)?;
Ok(QueryResult::Empty)
} else if let Some(name) = &drop.name {
Err(RouterError::ParseError(format!(
"Named index '{}' not supported. Use: DROP INDEX ON table(column)",
name.name
)))
} else {
Err(RouterError::ParseError(
"Invalid DROP INDEX syntax".to_string(),
))
}
},
StatementKind::ShowTables => {
let tables = self.relational.list_tables();
Ok(QueryResult::TableList(tables))
},
StatementKind::ShowEmbeddings { limit } => {
let limit_val = limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(100);
let keys = self.vector.list_keys();
let limited: Vec<String> = keys.into_iter().take(limit_val).collect();
Ok(QueryResult::Value(format!("Embeddings: {limited:?}")))
},
StatementKind::ShowVectorIndex => match &self.hnsw_index {
Some((_, keys)) => {
let count = keys.len();
Ok(QueryResult::Value(format!(
"HNSW index: {count} vectors indexed"
)))
},
None => Ok(QueryResult::Value("No HNSW index built".to_string())),
},
StatementKind::CountEmbeddings => {
let count = self.vector.list_keys().len();
Ok(QueryResult::Count(count))
},
StatementKind::Describe(desc) => self.exec_describe(desc),
StatementKind::Node(node) => self.exec_node(node),
StatementKind::Edge(edge) => self.exec_edge(edge),
StatementKind::Neighbors(neighbors) => self.exec_neighbors(neighbors),
StatementKind::Path(path) => self.exec_path(path),
StatementKind::Embed(embed) => self.exec_embed(embed),
StatementKind::Similar(similar) => self.exec_similar(similar),
StatementKind::Spatial(spatial) => self.exec_spatial(spatial),
StatementKind::Find(find) => self.exec_find(find),
StatementKind::Entity(entity) => self.exec_entity(entity),
StatementKind::Vault(vault) => self.exec_vault(vault),
StatementKind::Cache(cache) => self.exec_cache(cache),
StatementKind::Blob(blob) => self.exec_blob(blob),
StatementKind::Blobs(blobs) => self.exec_blobs(blobs),
StatementKind::Checkpoint(cp) => self.exec_checkpoint(cp),
StatementKind::Rollback(rb) => self.exec_rollback(rb),
StatementKind::Checkpoints(cps) => self.exec_checkpoints(cps),
StatementKind::Chain(chain) => self.exec_chain(chain),
StatementKind::Cluster(cluster) => self.exec_cluster(cluster),
StatementKind::GraphAlgorithm(algo) => self.exec_graph_algorithm(algo),
StatementKind::GraphConstraint(constraint) => self.exec_graph_constraint(constraint),
StatementKind::GraphIndex(idx) => self.exec_graph_index(idx),
StatementKind::GraphAggregate(agg) => self.exec_graph_aggregate(agg),
StatementKind::GraphPattern(pattern) => self.exec_graph_pattern(pattern),
StatementKind::GraphBatch(batch) => self.exec_graph_batch(batch),
StatementKind::CypherMatch(stmt) => cypher::exec_cypher_match(&self.graph, stmt),
StatementKind::CypherCreate(stmt) => cypher::exec_cypher_create(&self.graph, stmt),
StatementKind::CypherDelete(stmt) => cypher::exec_cypher_delete(&self.graph, stmt),
StatementKind::CypherMerge(stmt) => cypher::exec_cypher_merge(&self.graph, stmt),
StatementKind::Empty => Ok(QueryResult::Empty),
}
}
fn exec_describe(&self, desc: &DescribeStmt) -> Result<QueryResult> {
match &desc.target {
DescribeTarget::Table(name) => {
use std::fmt::Write;
let schema = self.relational.get_schema(&name.name)?;
let mut info = format!("Table: {}\n", name.name);
info.push_str("Columns:\n");
for col in &schema.columns {
let _ = writeln!(
info,
" {} {:?}{}",
col.name,
col.column_type,
if col.nullable { "" } else { " NOT NULL" }
);
}
Ok(QueryResult::Value(info))
},
DescribeTarget::Node(label) => {
let total_nodes = self.graph.node_count();
Ok(QueryResult::Value(format!(
"Node label '{}': Use NODE LIST {} to see nodes. Total nodes in graph: {}",
label.name, label.name, total_nodes
)))
},
DescribeTarget::Edge(edge_type) => {
let total_edges = self.graph.edge_count();
Ok(QueryResult::Value(format!(
"Edge type '{}': Use EDGE LIST {} to see edges. Total edges in graph: {}",
edge_type.name, edge_type.name, total_edges
)))
},
}
}
#[allow(clippy::too_many_lines)] fn exec_cache(&self, stmt: &CacheStmt) -> Result<QueryResult> {
let _identity = self.require_identity()?;
let cache = self
.cache
.as_ref()
.ok_or_else(|| RouterError::CacheError("Cache not initialized".to_string()))?;
match &stmt.operation {
CacheOp::Init => {
Ok(QueryResult::Value("Cache initialized".to_string()))
},
CacheOp::Stats => {
let stats = cache.stats();
let (tokens_in, tokens_out) = stats.tokens_saved();
let output = format!(
"Cache Statistics:\n\
Exact: {} hits, {} misses\n\
Semantic: {} hits, {} misses\n\
Embedding: {} hits, {} misses\n\
Tokens saved: {} in, {} out\n\
Evictions: {}",
stats.hits(CacheLayer::Exact),
stats.misses(CacheLayer::Exact),
stats.hits(CacheLayer::Semantic),
stats.misses(CacheLayer::Semantic),
stats.hits(CacheLayer::Embedding),
stats.misses(CacheLayer::Embedding),
tokens_in,
tokens_out,
stats.evictions(),
);
Ok(QueryResult::Value(output))
},
CacheOp::Clear => {
let entry_count = cache.stats().total_entries();
let op = DestructiveOp::CacheClear { entry_count };
match self.protect_destructive_op(
"CACHE CLEAR",
op,
vec![format!("{} cached entries", entry_count)],
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
cache.clear();
Ok(QueryResult::Value("Cache cleared".to_string()))
},
CacheOp::Evict { count } => {
let count_val = match count {
Some(expr) => self.expr_to_usize(expr)?,
None => 100, };
let evicted = cache.evict(count_val);
Ok(QueryResult::Value(format!("Evicted {evicted} entries")))
},
CacheOp::Get { key } => {
let key_str = self.expr_to_string(key)?;
Ok(QueryResult::Value(
cache
.get_simple(&key_str)
.unwrap_or_else(|| "(not found)".to_string()),
))
},
CacheOp::Put { key, value } => {
let key_str = self.expr_to_string(key)?;
let value_str = self.expr_to_string(value)?;
cache
.put_simple(&key_str, &value_str)
.map_err(|e| RouterError::CacheError(e.to_string()))?;
Ok(QueryResult::Value("OK".to_string()))
},
CacheOp::SemanticGet { query, threshold } => {
let query_str = self.expr_to_string(query)?;
let embedding = self.vector.get_embedding(&query_str).ok();
let _threshold = threshold
.as_ref()
.map(|e| self.expr_to_f32(e))
.transpose()?;
match cache.get(&query_str, embedding.as_deref()) {
Some(hit) => {
let similarity_str = hit
.similarity
.map(|s| format!(", similarity: {s:.4}"))
.unwrap_or_default();
Ok(QueryResult::Value(format!(
"response: {}, layer: {:?}{}",
hit.response, hit.layer, similarity_str
)))
},
None => Ok(QueryResult::Value("(not found)".to_string())),
}
},
CacheOp::SemanticPut {
query,
response,
embedding,
} => {
let query_str = self.expr_to_string(query)?;
let response_str = self.expr_to_string(response)?;
let emb: Vec<f32> = embedding
.iter()
.map(|e| self.expr_to_f32(e))
.collect::<Result<_>>()?;
cache
.put(&query_str, &emb, &response_str, "manual", None)
.map_err(|e| RouterError::CacheError(e.to_string()))?;
Ok(QueryResult::Value("OK".to_string()))
},
}
}
const fn is_cacheable_statement(stmt: &Statement) -> bool {
matches!(
&stmt.kind,
StatementKind::Select(_)
| StatementKind::Similar(_)
| StatementKind::Neighbors(_)
| StatementKind::Path(_)
)
}
#[allow(clippy::match_same_arms)] const fn is_write_statement(stmt: &Statement) -> bool {
match &stmt.kind {
StatementKind::Insert(_)
| StatementKind::Update(_)
| StatementKind::Delete(_)
| StatementKind::CreateTable(_)
| StatementKind::DropTable(_)
| StatementKind::CreateIndex(_)
| StatementKind::DropIndex(_) => true,
StatementKind::GraphBatch(_)
| StatementKind::GraphConstraint(_)
| StatementKind::GraphIndex(_)
| StatementKind::CypherCreate(_)
| StatementKind::CypherDelete(_)
| StatementKind::CypherMerge(_)
| StatementKind::Rollback(_) => true,
StatementKind::Node(n) => {
matches!(&n.operation, NodeOp::Create { .. } | NodeOp::Delete { .. })
},
StatementKind::Edge(e) => {
matches!(&e.operation, EdgeOp::Create { .. } | EdgeOp::Delete { .. })
},
StatementKind::Embed(e) => matches!(
&e.operation,
EmbedOp::Store { .. } | EmbedOp::Delete { .. } | EmbedOp::Batch { .. }
),
StatementKind::Spatial(s) => {
matches!(&s.op, SpatialOp::Insert { .. } | SpatialOp::Delete { .. })
},
StatementKind::Entity(e) => matches!(
&e.operation,
EntityOp::Create { .. }
| EntityOp::Update { .. }
| EntityOp::Delete { .. }
| EntityOp::Connect { .. }
| EntityOp::Batch { .. }
),
StatementKind::Vault(v) => matches!(
&v.operation,
VaultOp::Set { .. } | VaultOp::Delete { .. } | VaultOp::Rotate { .. }
),
StatementKind::Blob(b) => matches!(
&b.operation,
BlobOp::Put { .. }
| BlobOp::Delete { .. }
| BlobOp::Link { .. }
| BlobOp::Unlink { .. }
| BlobOp::Tag { .. }
| BlobOp::Untag { .. }
| BlobOp::MetaSet { .. }
),
_ => false,
}
}
fn cache_key_for_query(command: &str) -> String {
format!("query:{}", command.trim().to_lowercase())
}
fn try_cache_get(&self, command: &str) -> Option<QueryResult> {
let cache = self.cache.as_ref()?;
let key = Self::cache_key_for_query(command);
let json = cache.get_simple(&key)?;
serde_json::from_str(&json).ok()
}
fn try_cache_put(&self, command: &str, result: &QueryResult) {
if let Some(cache) = self.cache.as_ref() {
let key = Self::cache_key_for_query(command);
if let Ok(json) = serde_json::to_string(result) {
let _ = cache.put_simple(&key, &json);
}
}
}
fn invalidate_cache_on_write(&self) {
if let Some(cache) = self.cache.as_ref() {
cache.clear();
}
}
fn exec_vault(&self, stmt: &VaultStmt) -> Result<QueryResult> {
let vault = self
.vault
.as_ref()
.ok_or_else(|| RouterError::VaultError("Vault not initialized".to_string()))?;
let identity = self.require_identity()?;
match &stmt.operation {
VaultOp::Set { key, value } => {
let key_str = self.eval_string_expr(key)?;
let value_str = self.eval_string_expr(value)?;
vault.set(identity, &key_str, &value_str)?;
Ok(QueryResult::Empty)
},
VaultOp::Get { key } => {
let key_str = self.eval_string_expr(key)?;
let value = vault.get(identity, &key_str)?;
Ok(QueryResult::Value(value))
},
VaultOp::Delete { key } => {
let key_str = self.eval_string_expr(key)?;
let op = DestructiveOp::VaultDelete {
key: key_str.clone(),
};
match self.protect_destructive_op(
&format!("VAULT DELETE '{key_str}'"),
op,
vec![format!("secret key: {}", key_str)],
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
vault.delete(identity, &key_str)?;
Ok(QueryResult::Empty)
},
VaultOp::List { pattern } => {
let pat = pattern
.as_ref()
.map(|p| self.eval_string_expr(p))
.transpose()?
.unwrap_or_else(|| "*".to_string());
let keys = vault.list(identity, &pat)?;
Ok(QueryResult::Value(keys.join("\n")))
},
VaultOp::Rotate { key, new_value } => {
let key_str = self.eval_string_expr(key)?;
let new_value_str = self.eval_string_expr(new_value)?;
vault.rotate(identity, &key_str, &new_value_str)?;
Ok(QueryResult::Empty)
},
VaultOp::Grant { entity, key } => {
let entity_str = self.eval_string_expr(entity)?;
let key_str = self.eval_string_expr(key)?;
vault.grant(identity, &entity_str, &key_str)?;
Ok(QueryResult::Empty)
},
VaultOp::Revoke { entity, key } => {
let entity_str = self.eval_string_expr(entity)?;
let key_str = self.eval_string_expr(key)?;
vault.revoke(identity, &entity_str, &key_str)?;
Ok(QueryResult::Empty)
},
}
}
#[allow(clippy::unused_self)] fn eval_string_expr(&self, expr: &Expr) -> Result<String> {
match &expr.kind {
ExprKind::Literal(Literal::String(s)) => Ok(s.clone()),
ExprKind::Ident(ident) => Ok(ident.name.clone()),
_ => Err(RouterError::InvalidArgument(
"Expected string literal or identifier".to_string(),
)),
}
}
#[allow(clippy::too_many_lines)] fn exec_blob(&self, stmt: &BlobStmt) -> Result<QueryResult> {
let _identity = self.require_identity()?;
if matches!(stmt.operation, BlobOp::Init) {
if self.blob.is_some() {
return Ok(QueryResult::Value(
"Blob store already initialized".to_string(),
));
}
return Err(RouterError::BlobError(
"Use router.init_blob() to initialize blob storage".to_string(),
));
}
let blob = self
.blob
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob store not initialized".to_string()))?;
let runtime = self
.blob_runtime
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob runtime not initialized".to_string()))?;
match &stmt.operation {
BlobOp::Init => unreachable!(), BlobOp::Put {
filename,
data,
from_path,
options,
} => {
let filename_str = self.eval_string_expr(filename)?;
let put_options = self.blob_options_to_put_options(options)?;
let blob_data = if let Some(data_expr) = data {
self.expr_to_bytes(data_expr)?
} else if let Some(path_expr) = from_path {
let path = self.eval_string_expr(path_expr)?;
std::fs::read(&path)
.map_err(|e| RouterError::BlobError(format!("Failed to read file: {e}")))?
} else {
return Err(RouterError::MissingArgument(
"PUT requires either DATA or FROM path".to_string(),
));
};
let artifact_id = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.put(&filename_str, &blob_data, put_options).await
})?;
Ok(QueryResult::Value(artifact_id))
},
BlobOp::Get {
artifact_id,
to_path,
} => {
let id = self.eval_string_expr(artifact_id)?;
let data = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.get(&id).await
})?;
if let Some(path_expr) = to_path {
let path = self.eval_string_expr(path_expr)?;
std::fs::write(&path, &data).map_err(|e| {
RouterError::BlobError(format!("Failed to write file: {e}"))
})?;
Ok(QueryResult::Value(format!(
"Written {} bytes to {path}",
data.len()
)))
} else {
Ok(QueryResult::Blob(data))
}
},
BlobOp::Delete { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let size = runtime
.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.metadata(&id).await
})
.map(|m| m.size)
.unwrap_or(0);
let op = DestructiveOp::BlobDelete {
artifact_id: id.clone(),
size,
};
match self.protect_destructive_op(
&format!("BLOB DELETE '{id}'"),
op,
vec![format!("artifact: {}, size: {} bytes", id, size)],
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.delete(&id).await
})?;
Ok(QueryResult::Empty)
},
BlobOp::Info { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let meta = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.metadata(&id).await
})?;
Ok(QueryResult::ArtifactInfo(ArtifactInfoResult {
id: meta.id,
filename: meta.filename,
content_type: meta.content_type,
size: meta.size,
checksum: meta.checksum,
chunk_count: meta.chunk_count,
created: meta.created,
modified: meta.modified,
created_by: meta.created_by,
tags: meta.tags,
linked_to: meta.linked_to,
custom: meta.custom,
}))
},
BlobOp::Link {
artifact_id,
entity,
} => {
let id = self.eval_string_expr(artifact_id)?;
let entity_str = self.eval_string_expr(entity)?;
runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.link(&id, &entity_str).await
})?;
Ok(QueryResult::Empty)
},
BlobOp::Unlink {
artifact_id,
entity,
} => {
let id = self.eval_string_expr(artifact_id)?;
let entity_str = self.eval_string_expr(entity)?;
runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.unlink(&id, &entity_str).await
})?;
Ok(QueryResult::Empty)
},
BlobOp::Links { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let links = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.links(&id).await
})?;
Ok(QueryResult::ArtifactList(links))
},
BlobOp::Tag { artifact_id, tag } => {
let id = self.eval_string_expr(artifact_id)?;
let tag_str = self.eval_string_expr(tag)?;
runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.tag(&id, &tag_str).await
})?;
Ok(QueryResult::Empty)
},
BlobOp::Untag { artifact_id, tag } => {
let id = self.eval_string_expr(artifact_id)?;
let tag_str = self.eval_string_expr(tag)?;
runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.untag(&id, &tag_str).await
})?;
Ok(QueryResult::Empty)
},
BlobOp::Verify { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let valid = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.verify(&id)
})?;
Ok(QueryResult::Value(if valid {
"OK".to_string()
} else {
"INVALID".to_string()
}))
},
BlobOp::Gc { full } => {
let stats = runtime.block_on(async {
let blob_guard = blob.lock().await;
if *full {
blob_guard.full_gc().await
} else {
blob_guard.gc().await
}
})?;
Ok(QueryResult::Value(format!(
"Deleted {} chunks, freed {} bytes",
stats.deleted, stats.freed_bytes
)))
},
BlobOp::Repair => {
let stats = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.repair()
})?;
Ok(QueryResult::Value(format!(
"Fixed {} refs, deleted {} orphans",
stats.refs_fixed, stats.orphans_deleted
)))
},
BlobOp::Stats => {
let stats = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.stats().await
})?;
Ok(QueryResult::BlobStats(BlobStatsResult {
artifact_count: stats.artifact_count,
chunk_count: stats.chunk_count,
total_bytes: stats.total_bytes,
unique_bytes: stats.unique_bytes,
dedup_ratio: stats.dedup_ratio,
orphaned_chunks: stats.orphaned_chunks,
}))
},
BlobOp::MetaSet {
artifact_id,
key,
value,
} => {
let id = self.eval_string_expr(artifact_id)?;
let key_str = self.eval_string_expr(key)?;
let value_str = self.eval_string_expr(value)?;
runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.set_meta(&id, &key_str, &value_str).await
})?;
Ok(QueryResult::Empty)
},
BlobOp::MetaGet { artifact_id, key } => {
let id = self.eval_string_expr(artifact_id)?;
let key_str = self.eval_string_expr(key)?;
let value = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.get_meta(&id, &key_str).await
})?;
Ok(QueryResult::Value(
value.unwrap_or_else(|| "(not found)".to_string()),
))
},
}
}
fn exec_blobs(&self, stmt: &BlobsStmt) -> Result<QueryResult> {
let _identity = self.require_identity()?;
let blob = self
.blob
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob store not initialized".to_string()))?;
let runtime = self
.blob_runtime
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob runtime not initialized".to_string()))?;
match &stmt.operation {
BlobsOp::List { pattern } => {
let prefix = pattern
.as_ref()
.map(|p| self.eval_string_expr(p))
.transpose()?;
let ids = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.list(prefix.as_deref()).await
})?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::For { entity } => {
let entity_str = self.eval_string_expr(entity)?;
let ids = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.artifacts_for(&entity_str).await
})?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::ByTag { tag } => {
let tag_str = self.eval_string_expr(tag)?;
let ids = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.by_tag(&tag_str).await
})?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::ByType { content_type } => {
let ct = self.eval_string_expr(content_type)?;
let ids = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.by_content_type(&ct).await
})?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::Similar { artifact_id, limit } => {
let id = self.eval_string_expr(artifact_id)?;
let k = limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(10);
let similar = runtime.block_on(async {
let blob_guard = blob.lock().await;
blob_guard.similar(&id, k).await
})?;
Ok(QueryResult::Similar(
similar
.into_iter()
.map(|s| SimilarResult {
key: s.id,
score: s.similarity,
})
.collect(),
))
},
}
}
fn blob_options_to_put_options(
&self,
options: &BlobOptions,
) -> Result<tensor_blob::PutOptions> {
let mut put_options = tensor_blob::PutOptions::new();
if let Some(ct) = &options.content_type {
put_options = put_options.with_content_type(&self.eval_string_expr(ct)?);
}
if let Some(cb) = &options.created_by {
put_options = put_options.with_created_by(&self.eval_string_expr(cb)?);
}
for link_expr in &options.link {
let link = self.eval_string_expr(link_expr)?;
put_options = put_options.with_link(&link);
}
for tag_expr in &options.tag {
let tag = self.eval_string_expr(tag_expr)?;
put_options = put_options.with_tag(&tag);
}
Ok(put_options)
}
#[allow(clippy::unused_self)] fn expr_to_bytes(&self, expr: &Expr) -> Result<Vec<u8>> {
match &expr.kind {
ExprKind::Literal(Literal::String(s)) => Ok(s.as_bytes().to_vec()),
_ => Err(RouterError::InvalidArgument(
"Expected string literal for blob data".to_string(),
)),
}
}
fn exec_checkpoint(&self, stmt: &CheckpointStmt) -> Result<QueryResult> {
let checkpoint = self.checkpoint.as_ref().ok_or_else(|| {
RouterError::CheckpointError("Checkpoint manager not initialized".to_string())
})?;
let name = stmt
.name
.as_ref()
.map(|e| self.eval_string_expr(e))
.transpose()?;
let store = self.vector.store();
let checkpoint_id = checkpoint.create(name.as_deref(), store)?;
Ok(QueryResult::Value(format!(
"Checkpoint created: {checkpoint_id}"
)))
}
fn exec_rollback(&self, stmt: &RollbackStmt) -> Result<QueryResult> {
let checkpoint = self.checkpoint.as_ref().ok_or_else(|| {
RouterError::CheckpointError("Checkpoint manager not initialized".to_string())
})?;
let target = self.eval_string_expr(&stmt.target)?;
let store = self.vector.store();
checkpoint.rollback(&target, store)?;
Ok(QueryResult::Value(format!(
"Rolled back to checkpoint: {target}"
)))
}
fn exec_checkpoints(&self, stmt: &CheckpointsStmt) -> Result<QueryResult> {
let checkpoint = self.checkpoint.as_ref().ok_or_else(|| {
RouterError::CheckpointError("Checkpoint manager not initialized".to_string())
})?;
let limit = stmt
.limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?;
let limit_opt = limit.or(Some(10));
let checkpoints = checkpoint.list(limit_opt)?;
let info_list: Vec<CheckpointInfo> = checkpoints
.into_iter()
.map(|cp| CheckpointInfo {
id: cp.id,
name: cp.name,
created_at: cp.created_at,
is_auto: cp.trigger.is_some(),
})
.collect();
Ok(QueryResult::CheckpointList(info_list))
}
#[allow(clippy::too_many_lines)] fn exec_chain(&self, stmt: &ChainStmt) -> Result<QueryResult> {
let _identity = self.require_identity()?;
let chain = self
.chain
.as_ref()
.ok_or_else(|| RouterError::ChainError("Chain not initialized".to_string()))?;
match &stmt.operation {
ChainOp::Begin => {
let workspace = chain.begin()?;
Ok(QueryResult::Chain(ChainResult::TransactionBegun {
tx_id: workspace.id().to_string(),
}))
},
ChainOp::Commit => {
Ok(QueryResult::Chain(ChainResult::Committed {
block_hash: "pending".to_string(),
height: chain.height(),
}))
},
ChainOp::Rollback { height } => {
let h = self.expr_to_u64(height)?;
Ok(QueryResult::Chain(ChainResult::RolledBack { to_height: h }))
},
ChainOp::History { key } => {
let key_str = self.eval_string_expr(key)?;
let history = chain.history(&key_str)?;
let entries: Vec<ChainHistoryEntry> = history
.into_iter()
.map(|(height, tx)| ChainHistoryEntry {
height,
transaction_type: format!("{tx:?}"),
data: None,
})
.collect();
Ok(QueryResult::Chain(ChainResult::History(entries)))
},
ChainOp::Similar { embedding, limit } => {
let _embedding: Vec<f32> = embedding
.iter()
.map(|e| self.expr_to_f32(e))
.collect::<Result<Vec<_>>>()?;
let _limit = limit.as_ref().map(|e| self.expr_to_usize(e)).transpose()?;
Ok(QueryResult::Chain(ChainResult::Similar(vec![])))
},
ChainOp::Drift {
from_height,
to_height,
} => {
let from_h = self.expr_to_u64(from_height)?;
let to_h = self.expr_to_u64(to_height)?;
Ok(QueryResult::Chain(ChainResult::Drift(ChainDriftResult {
from_height: from_h,
to_height: to_h,
total_drift: 0.0,
avg_drift_per_block: 0.0,
max_drift: 0.0,
})))
},
ChainOp::Height => {
let height = chain.height();
Ok(QueryResult::Chain(ChainResult::Height(height)))
},
ChainOp::Tip => {
let hash = chain.tip_hash();
let height = chain.height();
Ok(QueryResult::Chain(ChainResult::Tip {
hash: hex::encode(hash),
height,
}))
},
ChainOp::Block { height } => {
let h = self.expr_to_u64(height)?;
if let Some(block) = chain.get_block(h)? {
Ok(QueryResult::Chain(ChainResult::Block(ChainBlockInfo {
height: h,
hash: hex::encode(block.hash()),
prev_hash: hex::encode(block.header.prev_hash),
timestamp: block.header.timestamp,
transaction_count: block.transactions.len(),
proposer: block.header.proposer,
})))
} else {
Err(RouterError::ChainError(format!("Block {h} not found")))
}
},
ChainOp::Verify => match chain.verify() {
Ok(()) => Ok(QueryResult::Chain(ChainResult::Verified {
ok: true,
errors: vec![],
})),
Err(e) => Ok(QueryResult::Chain(ChainResult::Verified {
ok: false,
errors: vec![e.to_string()],
})),
},
ChainOp::ShowCodebookGlobal => {
Ok(QueryResult::Chain(ChainResult::Codebook(
ChainCodebookInfo {
scope: "global".to_string(),
entry_count: 0,
dimension: 0,
domain: None,
},
)))
},
ChainOp::ShowCodebookLocal { domain } => {
let domain_str = self.eval_string_expr(domain)?;
Ok(QueryResult::Chain(ChainResult::Codebook(
ChainCodebookInfo {
scope: "local".to_string(),
entry_count: 0,
dimension: 0,
domain: Some(domain_str),
},
)))
},
ChainOp::AnalyzeTransitions => Ok(QueryResult::Chain(ChainResult::TransitionAnalysis(
ChainTransitionAnalysis {
total_transitions: 0,
valid_transitions: 0,
invalid_transitions: 0,
avg_validity_score: 0.0,
},
))),
}
}
#[allow(clippy::too_many_lines)] fn exec_cluster(&self, stmt: &ClusterStmt) -> Result<QueryResult> {
match &stmt.operation {
ClusterOp::Connect { addresses } => {
let addr_str = self.eval_string_expr(addresses)?;
Err(RouterError::InvalidArgument(format!(
"CLUSTER CONNECT '{addr_str}' requires shell support. Use the shell command or call router.init_cluster() from code."
)))
},
ClusterOp::Disconnect => {
if self.cluster.is_none() {
return Err(RouterError::InvalidArgument(
"Not connected to cluster".to_string(),
));
}
Err(RouterError::InvalidArgument(
"CLUSTER DISCONNECT requires shell support. Use the shell command or call router.shutdown_cluster() from code.".to_string(),
))
},
ClusterOp::Status => {
self.cluster.as_ref().map_or_else(
|| {
Ok(QueryResult::Value(
"Cluster: single-node mode (not connected)".to_string(),
))
},
|cluster| {
let node_id = cluster.node_id();
let is_leader = cluster.is_leader();
let leader = cluster
.current_leader()
.unwrap_or_else(|| "(unknown)".to_string());
let height = cluster.chain_height();
let commit_idx = cluster.commit_index();
let status = format!(
"Cluster Status:\n Node ID: {}\n Role: {}\n Leader: {}\n Chain Height: {}\n Commit Index: {}",
node_id,
if is_leader { "Leader" } else { "Follower" },
leader,
height,
commit_idx
);
Ok(QueryResult::Value(status))
},
)
},
ClusterOp::Nodes => {
self.cluster.as_ref().map_or_else(
|| {
Ok(QueryResult::Value(
"No cluster nodes (single-node mode)".to_string(),
))
},
|cluster| {
let membership = cluster.membership();
let view = membership.view();
let local_id = cluster.node_id();
let mut nodes = vec![format!(" {} (self)", local_id)];
for node in &view.nodes {
if &node.node_id != local_id {
let status = if view.healthy_nodes.contains(&node.node_id) {
"healthy"
} else if view.failed_nodes.contains(&node.node_id) {
"failed"
} else {
"unknown"
};
nodes.push(format!(" {} - {}", node.node_id, status));
}
}
Ok(QueryResult::Value(format!(
"Cluster Nodes ({}):\n{}",
nodes.len(),
nodes.join("\n")
)))
},
)
},
ClusterOp::Leader => {
self.cluster.as_ref().map_or_else(
|| {
Ok(QueryResult::Value(
"No leader (single-node mode)".to_string(),
))
},
|cluster| {
let leader = cluster.current_leader();
let is_self = cluster.is_leader();
leader.map_or_else(
|| {
Ok(QueryResult::Value(
"No leader elected (election in progress)".to_string(),
))
},
|leader_id| {
if is_self {
Ok(QueryResult::Value(format!(
"Leader: {leader_id} (this node)"
)))
} else {
Ok(QueryResult::Value(format!("Leader: {leader_id}")))
}
},
)
},
)
},
}
}
#[allow(clippy::too_many_lines)] fn exec_graph_algorithm(&self, stmt: &GraphAlgorithmStmt) -> Result<QueryResult> {
match &stmt.operation {
GraphAlgorithmOp::PageRank {
damping,
tolerance,
max_iterations,
direction,
edge_type,
} => {
let config = PageRankConfig {
damping: damping
.as_ref()
.map(|e| self.expr_to_f64(e))
.transpose()?
.unwrap_or(0.85),
tolerance: tolerance
.as_ref()
.map(|e| self.expr_to_f64(e))
.transpose()?
.unwrap_or(1e-6),
max_iterations: max_iterations
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(100),
direction: direction
.as_ref()
.map_or(Direction::Outgoing, |d| self.convert_parsed_direction(d)),
edge_type: edge_type.as_ref().map(|e| e.name.clone()),
};
let result = self.graph.pagerank(Some(config))?;
let items: Vec<PageRankItem> = result
.scores
.into_iter()
.map(|(node_id, score)| PageRankItem { node_id, score })
.collect();
Ok(QueryResult::PageRank(PageRankResult {
items,
iterations: result.iterations,
convergence: result.convergence,
converged: result.converged,
}))
},
GraphAlgorithmOp::BetweennessCentrality {
sampling_ratio,
direction,
edge_type,
} => {
let config = CentralityConfig {
direction: direction
.as_ref()
.map_or(Direction::Both, |d| self.convert_parsed_direction(d)),
edge_type: edge_type.as_ref().map(|e| e.name.clone()),
sampling_ratio: sampling_ratio
.as_ref()
.map(|e| self.expr_to_f64(e))
.transpose()?
.unwrap_or(1.0),
max_iterations: 100,
tolerance: 1e-6,
};
let result = self.graph.betweenness_centrality(Some(config))?;
let items: Vec<CentralityItem> = result
.scores
.into_iter()
.map(|(node_id, score)| CentralityItem { node_id, score })
.collect();
Ok(QueryResult::Centrality(CentralityResult {
items,
centrality_type: CentralityType::Betweenness,
iterations: result.iterations,
converged: result.converged,
sample_count: result.sample_count,
}))
},
GraphAlgorithmOp::ClosenessCentrality {
direction,
edge_type,
} => {
let config = CentralityConfig {
direction: direction
.as_ref()
.map_or(Direction::Both, |d| self.convert_parsed_direction(d)),
edge_type: edge_type.as_ref().map(|e| e.name.clone()),
sampling_ratio: 1.0,
max_iterations: 100,
tolerance: 1e-6,
};
let result = self.graph.closeness_centrality(Some(config))?;
let items: Vec<CentralityItem> = result
.scores
.into_iter()
.map(|(node_id, score)| CentralityItem { node_id, score })
.collect();
Ok(QueryResult::Centrality(CentralityResult {
items,
centrality_type: CentralityType::Closeness,
iterations: result.iterations,
converged: result.converged,
sample_count: result.sample_count,
}))
},
GraphAlgorithmOp::EigenvectorCentrality {
max_iterations,
tolerance,
direction,
edge_type,
} => {
let config = CentralityConfig {
direction: direction
.as_ref()
.map_or(Direction::Both, |d| self.convert_parsed_direction(d)),
edge_type: edge_type.as_ref().map(|e| e.name.clone()),
sampling_ratio: 1.0,
max_iterations: max_iterations
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(100),
tolerance: tolerance
.as_ref()
.map(|e| self.expr_to_f64(e))
.transpose()?
.unwrap_or(1e-6),
};
let result = self.graph.eigenvector_centrality(Some(config))?;
let items: Vec<CentralityItem> = result
.scores
.into_iter()
.map(|(node_id, score)| CentralityItem { node_id, score })
.collect();
Ok(QueryResult::Centrality(CentralityResult {
items,
centrality_type: CentralityType::Eigenvector,
iterations: result.iterations,
converged: result.converged,
sample_count: result.sample_count,
}))
},
GraphAlgorithmOp::LouvainCommunities {
resolution,
max_passes,
direction,
edge_type,
} => {
let config = CommunityConfig {
direction: direction
.as_ref()
.map_or(Direction::Both, |d| self.convert_parsed_direction(d)),
edge_type: edge_type.as_ref().map(|e| e.name.clone()),
resolution: resolution
.as_ref()
.map(|e| self.expr_to_f64(e))
.transpose()?
.unwrap_or(1.0),
max_passes: max_passes
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(10),
max_iterations: 100,
seed: None,
};
let result = self.graph.louvain_communities(Some(config))?;
let items: Vec<CommunityItem> = result
.communities
.iter()
.map(|(&node_id, &community_id)| CommunityItem {
node_id,
community_id,
})
.collect();
Ok(QueryResult::Communities(CommunityResult {
items,
members: result.members,
community_count: result.community_count,
modularity: result.modularity,
passes: result.passes,
iterations: result.iterations,
}))
},
GraphAlgorithmOp::LabelPropagation {
max_iterations,
direction,
edge_type,
} => {
let config = CommunityConfig {
direction: direction
.as_ref()
.map_or(Direction::Both, |d| self.convert_parsed_direction(d)),
edge_type: edge_type.as_ref().map(|e| e.name.clone()),
resolution: 1.0,
max_passes: 10,
max_iterations: max_iterations
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(100),
seed: None,
};
let result = self.graph.label_propagation(Some(config))?;
let items: Vec<CommunityItem> = result
.communities
.iter()
.map(|(&node_id, &community_id)| CommunityItem {
node_id,
community_id,
})
.collect();
Ok(QueryResult::Communities(CommunityResult {
items,
members: result.members,
community_count: result.community_count,
modularity: result.modularity,
passes: result.passes,
iterations: result.iterations,
}))
},
}
}
fn exec_graph_constraint(&self, stmt: &GraphConstraintStmt) -> Result<QueryResult> {
match &stmt.operation {
GraphConstraintOp::Create {
name,
target,
property,
constraint_type,
} => {
let g_target = match target {
ConstraintTarget::Node { label } => {
label.as_ref().map_or(GConstraintTarget::AllNodes, |l| {
GConstraintTarget::NodeLabel(l.name.clone())
})
},
ConstraintTarget::Edge { edge_type } => {
edge_type.as_ref().map_or(GConstraintTarget::AllEdges, |t| {
GConstraintTarget::EdgeType(t.name.clone())
})
},
};
let g_type = match constraint_type {
ConstraintType::Unique => GConstraintType::Unique,
ConstraintType::Exists => GConstraintType::Exists,
ConstraintType::Type(t) => {
use graph_engine::PropertyValueType;
let type_name = t.to_uppercase();
match type_name.as_str() {
"INT" | "INTEGER" => {
GConstraintType::PropertyType(PropertyValueType::Int)
},
"FLOAT" | "DOUBLE" => {
GConstraintType::PropertyType(PropertyValueType::Float)
},
"BOOL" | "BOOLEAN" => {
GConstraintType::PropertyType(PropertyValueType::Bool)
},
_ => GConstraintType::PropertyType(PropertyValueType::String),
}
},
};
let constraint = Constraint {
name: name.name.clone(),
target: g_target,
property: property.name.clone(),
constraint_type: g_type,
};
self.graph.create_constraint(constraint)?;
Ok(QueryResult::Empty)
},
GraphConstraintOp::Drop { name } => {
self.graph.drop_constraint(&name.name)?;
Ok(QueryResult::Empty)
},
GraphConstraintOp::List => {
let constraints = self.graph.list_constraints();
let results: Vec<ConstraintInfo> = constraints
.into_iter()
.map(|c| ConstraintInfo {
name: c.name,
target: match c.target {
GConstraintTarget::NodeLabel(l) => format!("Node({l})"),
GConstraintTarget::EdgeType(t) => format!("Edge({t})"),
GConstraintTarget::AllNodes => "AllNodes".to_string(),
GConstraintTarget::AllEdges => "AllEdges".to_string(),
},
property: c.property,
constraint_type: match c.constraint_type {
GConstraintType::Unique => "UNIQUE".to_string(),
GConstraintType::Exists => "EXISTS".to_string(),
GConstraintType::PropertyType(t) => format!("TYPE({t:?})"),
},
})
.collect();
Ok(QueryResult::Constraints(results))
},
GraphConstraintOp::Get { name } => match self.graph.get_constraint(&name.name) {
Some(c) => {
let info = ConstraintInfo {
name: c.name,
target: match c.target {
GConstraintTarget::NodeLabel(l) => format!("Node({l})"),
GConstraintTarget::EdgeType(t) => format!("Edge({t})"),
GConstraintTarget::AllNodes => "AllNodes".to_string(),
GConstraintTarget::AllEdges => "AllEdges".to_string(),
},
property: c.property,
constraint_type: match c.constraint_type {
GConstraintType::Unique => "UNIQUE".to_string(),
GConstraintType::Exists => "EXISTS".to_string(),
GConstraintType::PropertyType(t) => format!("TYPE({t:?})"),
},
};
Ok(QueryResult::Constraints(vec![info]))
},
None => Ok(QueryResult::Constraints(vec![])),
},
}
}
fn exec_graph_index(&self, stmt: &GraphIndexStmt) -> Result<QueryResult> {
match &stmt.operation {
GraphIndexOp::CreateNodeProperty { property } => {
self.graph.create_node_property_index(&property.name)?;
Ok(QueryResult::Empty)
},
GraphIndexOp::CreateEdgeProperty { property } => {
self.graph.create_edge_property_index(&property.name)?;
Ok(QueryResult::Empty)
},
GraphIndexOp::CreateLabel => {
self.graph.create_label_index()?;
Ok(QueryResult::Empty)
},
GraphIndexOp::CreateEdgeType => {
self.graph.create_edge_type_index()?;
Ok(QueryResult::Empty)
},
GraphIndexOp::DropNode { property } => {
self.graph.drop_node_index(&property.name)?;
Ok(QueryResult::Empty)
},
GraphIndexOp::DropEdge { property } => {
self.graph.drop_edge_index(&property.name)?;
Ok(QueryResult::Empty)
},
GraphIndexOp::ShowNodeIndexes => {
let indexes = self.graph.get_indexed_node_properties();
Ok(QueryResult::GraphIndexes(indexes))
},
GraphIndexOp::ShowEdgeIndexes => {
let indexes = self.graph.get_indexed_edge_properties();
Ok(QueryResult::GraphIndexes(indexes))
},
}
}
fn exec_graph_aggregate(&self, stmt: &GraphAggregateStmt) -> Result<QueryResult> {
match &stmt.operation {
GraphAggregateOp::CountNodes { label } => {
let count = match label {
Some(l) => self.graph.count_nodes_by_label(&l.name)?,
None => self.graph.count_nodes(),
};
Ok(QueryResult::Aggregate(AggregateResultValue::Count(count)))
},
GraphAggregateOp::CountEdges { edge_type } => {
let count = match edge_type {
Some(t) => self.graph.count_edges_by_type(&t.name)?,
None => self.graph.count_edges(),
};
Ok(QueryResult::Aggregate(AggregateResultValue::Count(count)))
},
GraphAggregateOp::AggregateNodeProperty {
function,
property,
label,
..
} => {
let agg = match label {
Some(l) => self
.graph
.aggregate_node_property_by_label(&l.name, &property.name)?,
None => self.graph.aggregate_node_property(&property.name),
};
let result = match function {
AggregateFunction::Sum => AggregateResultValue::Sum(agg.sum.unwrap_or(0.0)),
AggregateFunction::Avg => AggregateResultValue::Avg(agg.avg.unwrap_or(0.0)),
AggregateFunction::Min => AggregateResultValue::Min(
self.property_value_to_f64(agg.min).unwrap_or(0.0),
),
AggregateFunction::Max => AggregateResultValue::Max(
self.property_value_to_f64(agg.max).unwrap_or(0.0),
),
AggregateFunction::Count => AggregateResultValue::Count(agg.count),
};
Ok(QueryResult::Aggregate(result))
},
GraphAggregateOp::AggregateEdgeProperty {
function,
property,
edge_type,
..
} => {
let agg = match edge_type {
Some(t) => self
.graph
.aggregate_edge_property_by_type(&t.name, &property.name)?,
None => self.graph.aggregate_edge_property(&property.name),
};
let result = match function {
AggregateFunction::Sum => AggregateResultValue::Sum(agg.sum.unwrap_or(0.0)),
AggregateFunction::Avg => AggregateResultValue::Avg(agg.avg.unwrap_or(0.0)),
AggregateFunction::Min => AggregateResultValue::Min(
self.property_value_to_f64(agg.min).unwrap_or(0.0),
),
AggregateFunction::Max => AggregateResultValue::Max(
self.property_value_to_f64(agg.max).unwrap_or(0.0),
),
AggregateFunction::Count => AggregateResultValue::Count(agg.count),
};
Ok(QueryResult::Aggregate(result))
},
}
}
fn exec_graph_pattern(&self, stmt: &GraphPatternStmt) -> Result<QueryResult> {
match &stmt.operation {
GraphPatternOp::Match { pattern, limit } => {
let gp = self.pattern_spec_to_graph_pattern(pattern, limit.as_ref())?;
let result = self.graph.match_pattern(&gp)?;
Ok(QueryResult::PatternMatch(
self.convert_pattern_match_result(&result),
))
},
GraphPatternOp::Count { pattern } => {
let gp = self.pattern_spec_to_graph_pattern(pattern, None)?;
let count = self.graph.count_pattern_matches(&gp)?;
Ok(QueryResult::Aggregate(AggregateResultValue::Count(count)))
},
GraphPatternOp::Exists { pattern } => {
let gp = self.pattern_spec_to_graph_pattern(pattern, None)?;
let exists = self.graph.pattern_exists(&gp)?;
Ok(QueryResult::Value(exists.to_string()))
},
}
}
fn pattern_spec_to_graph_pattern(
&self,
pattern: &parser::PatternSpec,
limit: Option<&Expr>,
) -> Result<graph_engine::Pattern> {
use graph_engine::{EdgePattern, NodePattern, PathPattern, Pattern};
if pattern.nodes.is_empty() {
return Err(RouterError::InvalidArgument(
"Pattern must have at least one node".to_string(),
));
}
let build_node_pattern = |spec: &parser::NodePatternSpec| -> NodePattern {
let mut np = NodePattern::new();
if let Some(alias) = &spec.alias {
np = np.variable(&alias.name);
}
if let Some(label) = &spec.label {
np = np.label(&label.name);
}
np
};
if pattern.edges.is_empty() {
let start = build_node_pattern(&pattern.nodes[0]);
let path = PathPattern::new(start, EdgePattern::new(), NodePattern::new());
let mut gp = Pattern::new(path);
if let Some(lim) = limit {
gp = gp.limit(self.expr_to_usize(lim)?);
}
return Ok(gp);
}
let first_edge = &pattern.edges[0];
let start_node = build_node_pattern(&pattern.nodes[first_edge.from_node]);
let edge =
EdgePattern::new().direction(self.convert_parsed_direction(&first_edge.direction));
let edge = if let Some(alias) = &first_edge.alias {
edge.variable(&alias.name)
} else {
edge
};
let edge = if let Some(et) = &first_edge.edge_type {
edge.edge_type(&et.name)
} else {
edge
};
let end_node = build_node_pattern(&pattern.nodes[first_edge.to_node]);
let mut path = PathPattern::new(start_node, edge, end_node);
for edge_spec in pattern.edges.iter().skip(1) {
let edge =
EdgePattern::new().direction(self.convert_parsed_direction(&edge_spec.direction));
let edge = if let Some(alias) = &edge_spec.alias {
edge.variable(&alias.name)
} else {
edge
};
let edge = if let Some(et) = &edge_spec.edge_type {
edge.edge_type(&et.name)
} else {
edge
};
let target_node = build_node_pattern(&pattern.nodes[edge_spec.to_node]);
path = path.extend(edge, target_node);
}
let mut gp = Pattern::new(path);
if let Some(lim) = limit {
gp = gp.limit(self.expr_to_usize(lim)?);
}
Ok(gp)
}
#[allow(clippy::unused_self)] fn convert_pattern_match_result(
&self,
result: &graph_engine::PatternMatchResult,
) -> PatternMatchResultValue {
use graph_engine::Binding;
let matches = result
.matches
.iter()
.map(|m| {
let bindings = m
.bindings
.iter()
.map(|(k, v)| {
let binding = match v {
Binding::Node(n) => BindingValue::Node {
id: n.id,
label: n.labels.join(", "),
},
Binding::Edge(e) => BindingValue::Edge {
id: e.id,
edge_type: e.edge_type.clone(),
from: e.from,
to: e.to,
},
Binding::Path(p) => BindingValue::Path {
nodes: p.nodes.clone(),
edges: p.edges.clone(),
length: p.nodes.len().saturating_sub(1),
},
};
(k.clone(), binding)
})
.collect();
PatternMatchBinding { bindings }
})
.collect();
PatternMatchResultValue {
matches,
stats: PatternMatchStatsValue {
matches_found: result.stats.matches_found,
nodes_evaluated: result.stats.nodes_evaluated,
edges_evaluated: result.stats.edges_evaluated,
truncated: result.stats.truncated,
},
}
}
#[allow(clippy::too_many_lines)] fn exec_graph_batch(&self, stmt: &GraphBatchStmt) -> Result<QueryResult> {
match &stmt.operation {
GraphBatchOp::CreateNodes { nodes } => {
let inputs: Vec<NodeInput> = nodes
.iter()
.map(|n| {
let props = n
.properties
.iter()
.map(|p| {
let pv = self
.expr_to_property_value(&p.value)
.unwrap_or(PropertyValue::Null);
(p.key.name.clone(), pv)
})
.collect();
NodeInput {
labels: n.labels.iter().map(|l| l.name.clone()).collect(),
properties: props,
}
})
.collect();
let result = self.graph.batch_create_nodes(inputs)?;
Ok(QueryResult::BatchResult(BatchOperationResult {
operation: "CREATE_NODES".to_string(),
affected_count: result.count,
created_ids: Some(result.created_ids),
}))
},
GraphBatchOp::CreateEdges { edges } => {
let inputs: Vec<EdgeInput> = edges
.iter()
.map(|e| {
let from_id = self.expr_to_u64(&e.from_id).unwrap_or(0);
let to_id = self.expr_to_u64(&e.to_id).unwrap_or(0);
let props = e
.properties
.iter()
.map(|p| {
let pv = self
.expr_to_property_value(&p.value)
.unwrap_or(PropertyValue::Null);
(p.key.name.clone(), pv)
})
.collect();
EdgeInput {
from: from_id,
to: to_id,
edge_type: e.edge_type.name.clone(),
properties: props,
directed: true,
}
})
.collect();
let result = self.graph.batch_create_edges(inputs)?;
Ok(QueryResult::BatchResult(BatchOperationResult {
operation: "CREATE_EDGES".to_string(),
affected_count: result.count,
created_ids: Some(result.created_ids),
}))
},
GraphBatchOp::DeleteNodes { ids } => {
let node_ids: Vec<u64> = ids
.iter()
.filter_map(|e| self.expr_to_u64(e).ok())
.collect();
if !node_ids.is_empty() {
let sample_data: Vec<String> =
node_ids.iter().map(|id| format!("node {id}")).collect();
let op = DestructiveOp::NodeDelete {
node_id: node_ids[0],
edge_count: node_ids.len().saturating_sub(1),
};
match self.protect_destructive_op(
&format!("BATCH DELETE NODES ({})", node_ids.len()),
op,
sample_data,
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
}
let result = self.graph.batch_delete_nodes(node_ids)?;
Ok(QueryResult::BatchResult(BatchOperationResult {
operation: "DELETE_NODES".to_string(),
affected_count: result.count,
created_ids: None,
}))
},
GraphBatchOp::DeleteEdges { ids } => {
let edge_ids: Vec<u64> = ids
.iter()
.filter_map(|e| self.expr_to_u64(e).ok())
.collect();
if !edge_ids.is_empty() {
let sample_data: Vec<String> =
edge_ids.iter().map(|id| format!("edge {id}")).collect();
let op = DestructiveOp::EdgeDelete {
edge_id: edge_ids[0],
};
match self.protect_destructive_op(
&format!("BATCH DELETE EDGES ({})", edge_ids.len()),
op,
sample_data,
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
}
let result = self.graph.batch_delete_edges(edge_ids)?;
Ok(QueryResult::BatchResult(BatchOperationResult {
operation: "DELETE_EDGES".to_string(),
affected_count: result.count,
created_ids: None,
}))
},
GraphBatchOp::UpdateNodes { updates } => {
#[allow(clippy::type_complexity)]
let update_inputs: Vec<(
u64,
Option<Vec<String>>,
HashMap<String, PropertyValue>,
)> = updates
.iter()
.filter_map(|u| {
let id = self.expr_to_u64(&u.id).ok()?;
let props: HashMap<String, PropertyValue> = u
.properties
.iter()
.map(|p| {
let pv = self
.expr_to_property_value(&p.value)
.unwrap_or(PropertyValue::Null);
(p.key.name.clone(), pv)
})
.collect();
Some((id, None, props))
})
.collect();
let count = self.graph.batch_update_nodes(update_inputs)?;
Ok(QueryResult::BatchResult(BatchOperationResult {
operation: "UPDATE_NODES".to_string(),
affected_count: count,
created_ids: None,
}))
},
}
}
#[allow(clippy::unused_self)] #[allow(clippy::trivially_copy_pass_by_ref)] const fn convert_parsed_direction(&self, dir: &ParsedDirection) -> Direction {
match dir {
ParsedDirection::Outgoing => Direction::Outgoing,
ParsedDirection::Incoming => Direction::Incoming,
ParsedDirection::Both => Direction::Both,
}
}
#[allow(clippy::unused_self)] fn expr_to_property_value(&self, expr: &Expr) -> Result<PropertyValue> {
match &expr.kind {
ExprKind::Literal(lit) => match lit {
Literal::Null => Ok(PropertyValue::Null),
Literal::Boolean(b) => Ok(PropertyValue::Bool(*b)),
Literal::Integer(i) => Ok(PropertyValue::Int(*i)),
Literal::Float(f) => Ok(PropertyValue::Float(*f)),
Literal::String(s) => Ok(PropertyValue::String(s.clone())),
},
ExprKind::Ident(ident) => Ok(PropertyValue::String(ident.name.clone())),
_ => Err(RouterError::InvalidArgument(
"Cannot convert expression to property value".to_string(),
)),
}
}
#[allow(clippy::unused_self)] #[allow(clippy::cast_precision_loss)] #[allow(clippy::needless_pass_by_value)] fn property_value_to_f64(&self, value: Option<PropertyValue>) -> Option<f64> {
match value {
Some(PropertyValue::Int(i)) => Some(i as f64),
Some(PropertyValue::Float(f)) => Some(f),
_ => None,
}
}
fn exec_select(&self, select: &SelectStmt) -> Result<QueryResult> {
let from = select
.from
.as_ref()
.ok_or_else(|| RouterError::MissingArgument("FROM clause".to_string()))?;
let table_name = match &from.table.kind {
TableRefKind::Table(ident) => &ident.name,
TableRefKind::Subquery(_) => {
return Err(RouterError::ParseError(
"Subqueries not yet supported".to_string(),
))
},
};
if !from.joins.is_empty() {
return self.exec_select_with_joins(select, table_name, from);
}
let condition = if let Some(ref where_expr) = select.where_clause {
self.expr_to_condition(where_expr)?
} else {
Condition::True
};
if let Some(agg_result) = self.try_exec_aggregates(select, table_name, &condition)? {
return Ok(agg_result);
}
let projection = self.extract_projection(&select.columns)?;
let options = ColumnarScanOptions {
projection,
prefer_columnar: true,
};
let mut rows = self
.relational
.select_columnar(table_name, condition, options)?;
if !select.order_by.is_empty() {
self.sort_rows(&mut rows, &select.order_by);
}
if let Some(ref offset_expr) = select.offset {
if let ExprKind::Literal(neumann_parser::Literal::Integer(n)) = &offset_expr.kind {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let offset = *n as usize; if offset < rows.len() {
rows = rows.into_iter().skip(offset).collect();
} else {
rows.clear();
}
}
}
if let Some(ref limit_expr) = select.limit {
if let ExprKind::Literal(neumann_parser::Literal::Integer(n)) = &limit_expr.kind {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let limit = *n as usize; rows.truncate(limit);
}
}
Ok(QueryResult::Rows(rows))
}
#[allow(clippy::too_many_lines)] fn exec_select_with_joins(
&self,
select: &SelectStmt,
left_table: &str,
from: &neumann_parser::FromClause,
) -> Result<QueryResult> {
if from.joins.len() > 1 {
return Err(RouterError::ParseError(
"Multiple JOINs not yet supported; use single JOIN".to_string(),
));
}
let join = &from.joins[0];
let right_table = match &join.table.kind {
TableRefKind::Table(ident) => &ident.name,
TableRefKind::Subquery(_) => {
return Err(RouterError::ParseError(
"Subquery JOINs not yet supported".to_string(),
))
},
};
let left_alias: &str = match &from.table.alias {
Some(a) => &a.name,
None => left_table,
};
let right_alias: &str = join.table.alias.as_ref().map_or(right_table, |a| &a.name);
let mut rows: Vec<Row> = match join.kind {
JoinKind::Inner => {
let (on_a, on_b) =
self.get_join_columns(join.condition.as_ref(), left_table, right_table)?;
let pairs = self
.relational
.join(left_table, right_table, &on_a, &on_b)?;
pairs
.into_iter()
.map(|(a, b)| self.merge_rows(Some(&a), Some(&b), left_alias, right_alias))
.collect()
},
JoinKind::Left => {
let (on_a, on_b) =
self.get_join_columns(join.condition.as_ref(), left_table, right_table)?;
let pairs = self
.relational
.left_join(left_table, right_table, &on_a, &on_b)?;
pairs
.into_iter()
.map(|(a, b)| self.merge_rows(Some(&a), b.as_ref(), left_alias, right_alias))
.collect()
},
JoinKind::Right => {
let (on_a, on_b) =
self.get_join_columns(join.condition.as_ref(), left_table, right_table)?;
let pairs = self
.relational
.right_join(left_table, right_table, &on_a, &on_b)?;
pairs
.into_iter()
.map(|(a, b)| self.merge_rows(a.as_ref(), Some(&b), left_alias, right_alias))
.collect()
},
JoinKind::Full => {
let (on_a, on_b) =
self.get_join_columns(join.condition.as_ref(), left_table, right_table)?;
let pairs = self
.relational
.full_join(left_table, right_table, &on_a, &on_b)?;
pairs
.into_iter()
.map(|(a, b)| self.merge_rows(a.as_ref(), b.as_ref(), left_alias, right_alias))
.collect()
},
JoinKind::Cross => {
let pairs = self.relational.cross_join(left_table, right_table)?;
pairs
.into_iter()
.map(|(a, b)| self.merge_rows(Some(&a), Some(&b), left_alias, right_alias))
.collect()
},
JoinKind::Natural => {
let pairs = self.relational.natural_join(left_table, right_table)?;
pairs
.into_iter()
.map(|(a, b)| self.merge_rows(Some(&a), Some(&b), left_alias, right_alias))
.collect()
},
};
if let Some(ref where_expr) = select.where_clause {
rows.retain(|row| self.evaluate_join_condition(where_expr, row));
}
if !select.order_by.is_empty() {
self.sort_rows(&mut rows, &select.order_by);
}
if let Some(ref offset_expr) = select.offset {
if let ExprKind::Literal(neumann_parser::Literal::Integer(n)) = &offset_expr.kind {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let offset = *n as usize; if offset < rows.len() {
rows = rows.into_iter().skip(offset).collect();
} else {
rows.clear();
}
}
}
if let Some(ref limit_expr) = select.limit {
if let ExprKind::Literal(neumann_parser::Literal::Integer(n)) = &limit_expr.kind {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let limit = *n as usize; rows.truncate(limit);
}
}
Ok(QueryResult::Rows(rows))
}
fn get_join_columns(
&self,
condition: Option<&JoinCondition>,
_left_table: &str,
_right_table: &str,
) -> Result<(String, String)> {
condition.map_or_else(
|| {
Err(RouterError::ParseError(
"JOIN requires ON or USING clause (except CROSS/NATURAL)".to_string(),
))
},
|cond| self.extract_join_columns(cond),
)
}
fn evaluate_join_condition(&self, expr: &Expr, row: &Row) -> bool {
match &expr.kind {
ExprKind::Binary(left, op, right) => {
let left_val = self.get_row_value(left, row);
let right_val = self.get_row_value(right, row);
match (left_val, right_val) {
(Some(l), Some(r)) => match op {
BinaryOp::Eq => l == r,
BinaryOp::Ne => l != r,
BinaryOp::Lt => {
self.compare_values(&l, &r) == Some(std::cmp::Ordering::Less)
},
BinaryOp::Le => matches!(
self.compare_values(&l, &r),
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
),
BinaryOp::Gt => {
self.compare_values(&l, &r) == Some(std::cmp::Ordering::Greater)
},
BinaryOp::Ge => matches!(
self.compare_values(&l, &r),
Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
),
BinaryOp::And => l.is_truthy() && r.is_truthy(),
BinaryOp::Or => l.is_truthy() || r.is_truthy(),
_ => false,
},
_ => false,
}
},
ExprKind::Ident(_) | ExprKind::Qualified(_, _) => {
self.get_row_value(expr, row).is_some_and(|v| v.is_truthy())
},
_ => true,
}
}
#[allow(clippy::unused_self)] #[allow(clippy::cast_precision_loss)] fn compare_values(&self, a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
match (a, b) {
(Value::Int(x), Value::Int(y)) => Some(x.cmp(y)),
(Value::Float(x), Value::Float(y)) => x.partial_cmp(y),
(Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y),
(Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)),
(Value::String(x), Value::String(y)) => Some(x.cmp(y)),
(Value::Bool(x), Value::Bool(y)) => Some(x.cmp(y)),
_ => None,
}
}
fn sort_rows(&self, rows: &mut [Row], order_by: &[neumann_parser::OrderByItem]) {
rows.sort_by(|a, b| {
for item in order_by {
let val_a = self.get_sort_value(&item.expr, a);
let val_b = self.get_sort_value(&item.expr, b);
let cmp =
self.compare_values_with_nulls(val_a.as_ref(), val_b.as_ref(), item.nulls);
let cmp = match item.direction {
SortDirection::Asc => cmp,
SortDirection::Desc => cmp.reverse(),
};
if cmp != std::cmp::Ordering::Equal {
return cmp;
}
}
std::cmp::Ordering::Equal
});
}
#[allow(clippy::unused_self)] fn get_sort_value(&self, expr: &Expr, row: &Row) -> Option<Value> {
match &expr.kind {
ExprKind::Ident(ident) => {
row.values
.iter()
.find(|(col, _)| col == &ident.name)
.or_else(|| {
row.values
.iter()
.find(|(col, _)| col.ends_with(&format!(".{}", ident.name)))
})
.map(|(_, v)| v.clone())
},
ExprKind::Qualified(table_expr, col) => {
if let ExprKind::Ident(table) = &table_expr.kind {
let full_name = format!("{}.{}", table.name, col.name);
row.values
.iter()
.find(|(c, _)| c == &full_name)
.map(|(_, v)| v.clone())
} else {
None
}
},
_ => None,
}
}
fn compare_values_with_nulls(
&self,
a: Option<&Value>,
b: Option<&Value>,
nulls_order: Option<NullsOrder>,
) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a, b) {
(None, None) | (Some(Value::Null), Some(Value::Null)) => Ordering::Equal,
(None | Some(Value::Null), _) => match nulls_order.unwrap_or(NullsOrder::Last) {
NullsOrder::First => Ordering::Less,
NullsOrder::Last => Ordering::Greater,
},
(_, None | Some(Value::Null)) => match nulls_order.unwrap_or(NullsOrder::Last) {
NullsOrder::First => Ordering::Greater,
NullsOrder::Last => Ordering::Less,
},
(Some(va), Some(vb)) => self.compare_values(va, vb).unwrap_or(Ordering::Equal),
}
}
fn try_exec_aggregates(
&self,
select: &SelectStmt,
table_name: &str,
condition: &Condition,
) -> Result<Option<QueryResult>> {
let mut aggregates: Vec<(String, AggregateFunc)> = Vec::new();
let mut non_agg_columns: Vec<(String, Expr)> = Vec::new();
for item in &select.columns {
if let Some(agg) = self.parse_aggregate(&item.expr) {
let alias = item.alias.as_ref().map_or_else(
|| self.aggregate_default_name(&item.expr),
|a| a.name.clone(),
);
aggregates.push((alias, agg));
} else {
let alias = item.alias.as_ref().map_or_else(
|| {
self.expr_to_column_name(&item.expr)
.unwrap_or_else(|_| "?".to_string())
},
|a| a.name.clone(),
);
non_agg_columns.push((alias, item.expr.clone()));
}
}
if !select.group_by.is_empty() {
return self.exec_grouped_aggregates(
select,
table_name,
condition,
&aggregates,
&non_agg_columns,
);
}
if aggregates.is_empty() {
return Ok(None);
}
let mut values: Vec<(String, Value)> = Vec::new();
for (alias, agg) in aggregates {
let val = match agg {
AggregateFunc::Count(col) => {
let count = if let Some(ref column_name) = col {
self.relational
.count_column(table_name, column_name, condition.clone())?
} else {
self.relational.count(table_name, condition.clone())?
};
#[allow(clippy::cast_possible_wrap)]
Value::Int(count as i64)
},
AggregateFunc::Sum(col) => {
let sum = self.relational.sum(table_name, &col, condition.clone())?;
Value::Float(sum)
},
AggregateFunc::Avg(col) => self
.relational
.avg(table_name, &col, condition.clone())?
.map_or(Value::Null, Value::Float),
AggregateFunc::Min(col) => self
.relational
.min(table_name, &col, condition.clone())?
.unwrap_or(Value::Null),
AggregateFunc::Max(col) => self
.relational
.max(table_name, &col, condition.clone())?
.unwrap_or(Value::Null),
};
values.push((alias, val));
}
let row = Row { id: 0, values };
Ok(Some(QueryResult::Rows(vec![row])))
}
fn exec_grouped_aggregates(
&self,
select: &SelectStmt,
table_name: &str,
condition: &Condition,
aggregates: &[(String, AggregateFunc)],
non_agg_columns: &[(String, Expr)],
) -> Result<Option<QueryResult>> {
use std::collections::HashMap;
let rows = self.relational.select_columnar(
table_name,
condition.clone(),
ColumnarScanOptions {
projection: None,
prefer_columnar: true,
},
)?;
let group_key_names: Vec<String> = select
.group_by
.iter()
.filter_map(|expr| self.expr_to_column_name(expr).ok())
.collect();
let mut groups: HashMap<String, (Vec<Value>, Vec<&Row>)> = HashMap::new();
for row in &rows {
let group_key: Vec<Value> = group_key_names
.iter()
.map(|col| {
row.values
.iter()
.find(|(c, _)| c == col)
.map_or(Value::Null, |(_, v)| v.clone())
})
.collect();
let key_str = self.values_to_group_key(&group_key);
groups
.entry(key_str)
.or_insert_with(|| (group_key, Vec::new()))
.1
.push(row);
}
let mut result_rows: Vec<Row> = Vec::new();
for (_, (_group_key, group_rows)) in groups {
let mut values: Vec<(String, Value)> = Vec::new();
for (alias, expr) in non_agg_columns {
let val = group_rows.first().map_or(Value::Null, |first_row| {
self.get_row_value(expr, first_row).unwrap_or(Value::Null)
});
values.push((alias.clone(), val));
}
for (alias, agg) in aggregates {
let val = self.compute_aggregate_for_group(agg, &group_rows);
values.push((alias.clone(), val));
}
let row = Row { id: 0, values };
if let Some(ref having_expr) = select.having {
if !self.evaluate_join_condition(having_expr, &row) {
continue;
}
}
result_rows.push(row);
}
Ok(Some(QueryResult::Rows(result_rows)))
}
#[allow(clippy::too_many_lines)] #[allow(clippy::unused_self)] fn compute_aggregate_for_group(&self, agg: &AggregateFunc, rows: &[&Row]) -> Value {
match agg {
AggregateFunc::Count(col) => {
let count = if col.is_none() {
rows.len() as u64
} else {
rows.iter()
.filter(|r| {
r.values.iter().any(|(c, v)| {
c == col.as_ref().unwrap() && !matches!(v, Value::Null)
})
})
.count() as u64
};
#[allow(clippy::cast_possible_wrap)]
Value::Int(count as i64)
},
AggregateFunc::Sum(col) => {
let mut sum = 0.0;
for row in rows {
if let Some((_, val)) = row.values.iter().find(|(c, _)| c == col) {
match val {
#[allow(clippy::cast_precision_loss)]
Value::Int(i) => sum += *i as f64,
Value::Float(f) => sum += *f,
_ => {},
}
}
}
Value::Float(sum)
},
AggregateFunc::Avg(col) => {
let mut sum = 0.0;
let mut count = 0;
for row in rows {
if let Some((_, val)) = row.values.iter().find(|(c, _)| c == col) {
match val {
Value::Int(i) => {
#[allow(clippy::cast_precision_loss)]
{
sum += *i as f64;
}
count += 1;
},
Value::Float(f) => {
sum += *f;
count += 1;
},
_ => {},
}
}
}
if count == 0 {
Value::Null
} else {
Value::Float(sum / f64::from(count))
}
},
AggregateFunc::Min(col) => {
let mut min_val: Option<Value> = None;
for row in rows {
if let Some((_, val)) = row.values.iter().find(|(c, _)| c == col) {
if matches!(val, Value::Null) {
continue;
}
min_val = Some(min_val.as_ref().map_or_else(
|| val.clone(),
|current| match (current, val) {
(Value::Int(a), Value::Int(b)) if b < a => val.clone(),
(Value::Float(a), Value::Float(b)) if b < a => val.clone(),
(Value::String(a), Value::String(b)) if b < a => val.clone(),
_ => current.clone(),
},
));
}
}
min_val.unwrap_or(Value::Null)
},
AggregateFunc::Max(col) => {
let mut max_val: Option<Value> = None;
for row in rows {
if let Some((_, val)) = row.values.iter().find(|(c, _)| c == col) {
if matches!(val, Value::Null) {
continue;
}
max_val = Some(max_val.as_ref().map_or_else(
|| val.clone(),
|current| match (current, val) {
(Value::Int(a), Value::Int(b)) if b > a => val.clone(),
(Value::Float(a), Value::Float(b)) if b > a => val.clone(),
(Value::String(a), Value::String(b)) if b > a => val.clone(),
_ => current.clone(),
},
));
}
}
max_val.unwrap_or(Value::Null)
},
}
}
#[allow(clippy::unused_self)] fn values_to_group_key(&self, values: &[Value]) -> String {
values
.iter()
.map(|v| match v {
Value::Null => "NULL".to_string(),
Value::Int(i) => format!("I:{i}"),
Value::Float(f) => format!("F:{f}"),
Value::String(s) => format!("S:{s}"),
Value::Bool(b) => format!("B:{b}"),
Value::Bytes(b) => format!("BY:{}", hex::encode(b)),
Value::Json(j) => format!("J:{j}"),
_ => "UNKNOWN".to_string(),
})
.collect::<Vec<_>>()
.join("|")
}
fn parse_aggregate(&self, expr: &Expr) -> Option<AggregateFunc> {
if let ExprKind::Call(call) = &expr.kind {
let name = call.name.name.to_uppercase();
match name.as_str() {
"COUNT" => {
if call.args.is_empty() || matches!(&call.args[0].kind, ExprKind::Wildcard) {
Some(AggregateFunc::Count(None))
} else {
let col = self.expr_to_column_name(&call.args[0]).ok()?;
Some(AggregateFunc::Count(Some(col)))
}
},
"SUM" => {
if call.args.is_empty() {
return None;
}
let col = self.expr_to_column_name(&call.args[0]).ok()?;
Some(AggregateFunc::Sum(col))
},
"AVG" => {
if call.args.is_empty() {
return None;
}
let col = self.expr_to_column_name(&call.args[0]).ok()?;
Some(AggregateFunc::Avg(col))
},
"MIN" => {
if call.args.is_empty() {
return None;
}
let col = self.expr_to_column_name(&call.args[0]).ok()?;
Some(AggregateFunc::Min(col))
},
"MAX" => {
if call.args.is_empty() {
return None;
}
let col = self.expr_to_column_name(&call.args[0]).ok()?;
Some(AggregateFunc::Max(col))
},
_ => None,
}
} else {
None
}
}
fn aggregate_default_name(&self, expr: &Expr) -> String {
if let ExprKind::Call(call) = &expr.kind {
let name = call.name.name.to_uppercase();
if call.args.is_empty() || matches!(&call.args[0].kind, ExprKind::Wildcard) {
format!("{name}(*)")
} else if let Ok(col) = self.expr_to_column_name(&call.args[0]) {
format!("{name}({col})")
} else {
format!("{name}(?)")
}
} else {
"?".to_string()
}
}
fn get_row_value(&self, expr: &Expr, row: &Row) -> Option<Value> {
match &expr.kind {
ExprKind::Literal(lit) => Some(match lit {
Literal::Null => Value::Null,
Literal::Boolean(b) => Value::Bool(*b),
Literal::Integer(i) => Value::Int(*i),
Literal::Float(f) => Value::Float(*f),
Literal::String(s) => Value::String(s.clone()),
}),
ExprKind::Ident(ident) => {
row.values
.iter()
.find(|(col, _)| {
col == &ident.name || col.ends_with(&format!(".{}", ident.name))
})
.map(|(_, v)| v.clone())
},
ExprKind::Qualified(table_expr, col) => {
if let ExprKind::Ident(table) = &table_expr.kind {
let full_name = format!("{}.{}", table.name, col.name);
row.values
.iter()
.find(|(c, _)| c == &full_name)
.map(|(_, v)| v.clone())
} else {
None
}
},
ExprKind::Call(_) => {
let col_name = self.aggregate_default_name(expr);
row.values
.iter()
.find(|(c, _)| c == &col_name)
.map(|(_, v)| v.clone())
},
_ => None,
}
}
#[allow(clippy::unused_self)] #[allow(clippy::unnecessary_wraps)] fn extract_projection(
&self,
items: &[neumann_parser::SelectItem],
) -> Result<Option<Vec<String>>> {
if items.len() == 1 && matches!(&items[0].expr.kind, ExprKind::Wildcard) {
return Ok(None);
}
for item in items {
if matches!(
&item.expr.kind,
ExprKind::Wildcard | ExprKind::QualifiedWildcard(_)
) {
return Ok(None);
}
}
let mut columns = Vec::with_capacity(items.len());
for item in items {
match &item.expr.kind {
ExprKind::Ident(ident) => {
columns.push(ident.name.clone());
},
ExprKind::Qualified(_, name) => {
columns.push(name.name.clone());
},
_ => {
return Ok(None);
},
}
}
Ok(Some(columns))
}
fn exec_insert(&self, insert: &InsertStmt) -> Result<QueryResult> {
match &insert.source {
InsertSource::Values(rows) => {
let mut ids = Vec::new();
for row_values in rows {
let mut values = HashMap::new();
if let Some(ref cols) = insert.columns {
for (col, val) in cols.iter().zip(row_values.iter()) {
values.insert(col.name.clone(), self.expr_to_value(val)?);
}
} else {
let schema = self.relational.get_schema(&insert.table.name)?;
for (col, val) in schema.columns.iter().zip(row_values.iter()) {
values.insert(col.name.clone(), self.expr_to_value(val)?);
}
}
let id = self.relational.insert(&insert.table.name, values)?;
ids.push(id);
}
Ok(QueryResult::Ids(ids))
},
InsertSource::Query(select) => {
let select_result = self.exec_select(select)?;
let QueryResult::Rows(rows) = select_result else {
return Err(RouterError::ParseError(
"INSERT ... SELECT query did not return rows".to_string(),
));
};
if rows.is_empty() {
return Ok(QueryResult::Ids(vec![]));
}
let schema = self.relational.get_schema(&insert.table.name)?;
let columns: Vec<String> = if let Some(ref cols) = insert.columns {
cols.iter().map(|c| c.name.clone()).collect()
} else {
schema.columns.iter().map(|c| c.name.clone()).collect()
};
let mut ids = Vec::new();
for row in rows {
let mut values: HashMap<String, Value> = HashMap::new();
for col in &columns {
if let Some(val) = row.get(col) {
values.insert(col.clone(), val.clone());
}
}
let id = self.relational.insert(&insert.table.name, values)?;
ids.push(id);
}
Ok(QueryResult::Ids(ids))
},
}
}
fn protect_destructive_op(
&self,
command: &str,
op: DestructiveOp,
sample_data: Vec<String>,
) -> ProtectedOpResult {
let Some(checkpoint) = self.checkpoint.as_ref() else {
return ProtectedOpResult::Proceed;
};
if !checkpoint.auto_checkpoint_enabled() {
return ProtectedOpResult::Proceed;
}
let preview = checkpoint.generate_preview(&op, sample_data);
if !checkpoint.request_confirmation(&op, &preview) {
return ProtectedOpResult::Cancelled;
}
let store = self.vector.store();
if let Err(e) = checkpoint.create_auto(command, op, preview, store) {
eprintln!("Warning: Failed to create auto-checkpoint: {e}");
}
ProtectedOpResult::Proceed
}
fn collect_delete_sample(
&self,
table: &str,
condition: &Condition,
limit: usize,
) -> (usize, Vec<String>) {
let Ok(rows) = self.relational.select(table, condition.clone()) else {
return (0, vec![]);
};
let count = rows.len();
let sample: Vec<String> = rows
.into_iter()
.take(limit)
.map(|row| {
let pairs: Vec<String> = row
.values
.iter()
.map(|(k, v)| format!("{k}={v:?}"))
.collect();
format!("_id={}, {}", row.id, pairs.join(", "))
})
.collect();
(count, sample)
}
fn collect_table_sample(&self, table: &str, limit: usize) -> (usize, Vec<String>) {
self.collect_delete_sample(table, &Condition::True, limit)
}
fn collect_node_info(&self, node_id: u64) -> (usize, Vec<String>) {
let edge_count = self
.graph
.neighbors(node_id, None, Direction::Both, None)
.map(|nodes| nodes.len())
.unwrap_or(0);
let sample = match self.graph.get_node(node_id) {
Ok(node) => {
let props: Vec<String> = node
.properties
.iter()
.take(3)
.map(|(k, v)| format!("{k}={v:?}"))
.collect();
vec![format!(
"label='{}', {}",
node.labels.join(":"),
props.join(", ")
)]
},
Err(_) => vec![],
};
(edge_count, sample)
}
fn collect_edge_info(&self, edge_id: u64) -> Vec<String> {
match self.graph.get_edge(edge_id) {
Ok(edge) => {
let props: Vec<String> = edge
.properties
.iter()
.take(3)
.map(|(k, v)| format!("{k}={v:?}"))
.collect();
vec![format!(
"type='{}', from={}, to={}, {}",
edge.edge_type,
edge.from,
edge.to,
props.join(", ")
)]
},
Err(_) => vec![],
}
}
fn exec_update(&self, update: &UpdateStmt) -> Result<QueryResult> {
let condition = if let Some(ref where_expr) = update.where_clause {
self.expr_to_condition(where_expr)?
} else {
Condition::True
};
let mut values = HashMap::new();
for assign in &update.assignments {
values.insert(
assign.column.name.clone(),
self.expr_to_value(&assign.value)?,
);
}
let count = self
.relational
.update(&update.table.name, condition, values)?;
Ok(QueryResult::Count(count))
}
fn exec_delete(&self, delete: &DeleteStmt) -> Result<QueryResult> {
let table = &delete.table.name;
let condition = if let Some(ref where_expr) = delete.where_clause {
self.expr_to_condition(where_expr)?
} else {
Condition::True
};
let (row_count, sample_data) = self.collect_delete_sample(table, &condition, 5);
if row_count > 0 {
let op = DestructiveOp::Delete {
table: table.clone(),
row_count,
};
let command = format!(
"DELETE FROM {}{}",
table,
if delete.where_clause.is_some() {
" WHERE ..."
} else {
""
}
);
match self.protect_destructive_op(&command, op, sample_data) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
}
let count = self.relational.delete_rows(table, condition)?;
Ok(QueryResult::Count(count))
}
fn exec_create_table(&self, create: &parser::CreateTableStmt) -> Result<QueryResult> {
if create.if_not_exists && self.relational.table_exists(&create.table.name) {
return Ok(QueryResult::Empty);
}
let mut columns = Vec::new();
for col in &create.columns {
let col_type = self.data_type_to_column_type(&col.data_type)?;
let mut column = relational_engine::Column::new(&col.name.name, col_type);
let is_nullable = !col
.constraints
.iter()
.any(|c| matches!(c, parser::ColumnConstraint::NotNull));
if is_nullable {
column = column.nullable();
}
columns.push(column);
}
let schema = relational_engine::Schema::new(columns);
self.relational.create_table(&create.table.name, schema)?;
Ok(QueryResult::Empty)
}
fn exec_node(&self, node: &NodeStmt) -> Result<QueryResult> {
match &node.operation {
NodeOp::Create { label, properties } => {
let props = self.properties_to_map(properties)?;
let id = self.graph.create_node(&label.name, props)?;
Ok(QueryResult::Ids(vec![id]))
},
NodeOp::Get { id } => {
let node_id = self.expr_to_u64(id)?;
let node = self.graph.get_node(node_id)?;
let properties: HashMap<String, String> = node
.properties
.iter()
.map(|(k, v)| (k.clone(), Self::property_to_string(v)))
.collect();
Ok(QueryResult::Nodes(vec![NodeResult {
id: node.id,
label: node.labels.join(":"),
properties,
}]))
},
NodeOp::Delete { id } => {
let node_id = self.expr_to_u64(id)?;
let (edge_count, sample_data) = self.collect_node_info(node_id);
let op = DestructiveOp::NodeDelete {
node_id,
edge_count,
};
match self.protect_destructive_op(
&format!("NODE DELETE {node_id}"),
op,
sample_data,
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
self.graph.delete_node(node_id)?;
Ok(QueryResult::Count(1))
},
NodeOp::List {
label,
limit,
offset,
} => {
let label_filter = label.as_ref().map(|l| l.name.as_str());
let limit_val = limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(1000);
let offset_val = offset
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(0);
let unified_items =
self.scan_find_nodes(label_filter, None, limit_val, offset_val)?;
let nodes: Vec<NodeResult> = unified_items
.into_iter()
.map(|item| {
let id = item.id.parse::<u64>().unwrap_or(0);
let label = item.data.get("label").cloned().unwrap_or_default();
let properties: HashMap<String, String> = item
.data
.into_iter()
.filter(|(k, _)| k != "label")
.collect();
NodeResult {
id,
label,
properties,
}
})
.collect();
Ok(QueryResult::Nodes(nodes))
},
}
}
fn exec_edge(&self, edge: &EdgeStmt) -> Result<QueryResult> {
match &edge.operation {
EdgeOp::Create {
from_id,
to_id,
edge_type,
properties,
} => {
let from = self.expr_to_u64(from_id)?;
let to = self.expr_to_u64(to_id)?;
let props = self.properties_to_map(properties)?;
let id = self
.graph
.create_edge(from, to, &edge_type.name, props, true)?;
Ok(QueryResult::Ids(vec![id]))
},
EdgeOp::Get { id } => {
let edge_id = self.expr_to_u64(id)?;
let edge = self.graph.get_edge(edge_id)?;
Ok(QueryResult::Edges(vec![EdgeResult {
id: edge.id,
from: edge.from,
to: edge.to,
label: edge.edge_type,
}]))
},
EdgeOp::Delete { id } => {
let edge_id = self.expr_to_u64(id)?;
let sample_data = self.collect_edge_info(edge_id);
let op = DestructiveOp::EdgeDelete { edge_id };
match self.protect_destructive_op(
&format!("EDGE DELETE {edge_id}"),
op,
sample_data,
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
self.graph.delete_edge(edge_id)?;
Ok(QueryResult::Count(1))
},
EdgeOp::List {
edge_type,
limit,
offset,
} => {
let type_filter = edge_type.as_ref().map(|t| t.name.as_str());
let limit_val = limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(1000);
let offset_val = offset
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(0);
let unified_items =
self.scan_find_edges(type_filter, None, limit_val, offset_val)?;
let edges: Vec<EdgeResult> = unified_items
.into_iter()
.map(|item| {
let id = item.id.parse::<u64>().unwrap_or(0);
let from = item
.data
.get("from")
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
let to = item
.data
.get("to")
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
let label = item.data.get("type").cloned().unwrap_or_default();
EdgeResult {
id,
from,
to,
label,
}
})
.collect();
Ok(QueryResult::Edges(edges))
},
}
}
fn exec_neighbors(&self, neighbors: &NeighborsStmt) -> Result<QueryResult> {
if let Some(ref similarity_vec) = neighbors.by_similarity {
let entity_key = self.expr_to_string(&neighbors.node_id)?;
let query: Vec<f32> = similarity_vec
.iter()
.map(|e| self.expr_to_f32(e))
.collect::<Result<_>>()?;
let top_k = neighbors
.limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(10);
let items = self.find_neighbors_by_similarity(&entity_key, &query, top_k)?;
let results: Vec<SimilarResult> = items
.into_iter()
.map(|item| SimilarResult {
key: item.id,
score: item.score.unwrap_or(0.0),
})
.collect();
return Ok(QueryResult::Similar(results));
}
let node_id = self.expr_to_u64(&neighbors.node_id)?;
let direction = match neighbors.direction {
ParsedDirection::Outgoing => Direction::Outgoing,
ParsedDirection::Incoming => Direction::Incoming,
ParsedDirection::Both => Direction::Both,
};
let edge_type = neighbors.edge_type.as_ref().map(|e| e.name.as_str());
let neighbor_nodes = self.graph.neighbors(node_id, edge_type, direction, None)?;
let neighbor_ids: Vec<u64> = neighbor_nodes.iter().map(|n| n.id).collect();
Ok(QueryResult::Ids(neighbor_ids))
}
fn exec_path(&self, path: &PathStmt) -> Result<QueryResult> {
let from = self.expr_to_u64(&path.from_id)?;
let to = self.expr_to_u64(&path.to_id)?;
match self.graph.find_path(from, to, None) {
Ok(path) => Ok(QueryResult::Path(path.nodes)),
Err(GraphError::PathNotFound) => Ok(QueryResult::Path(vec![])),
Err(e) => Err(e.into()),
}
}
fn exec_embed(&self, embed: &EmbedStmt) -> Result<QueryResult> {
let collection = embed.collection.as_deref();
match &embed.operation {
EmbedOp::Store { key, vector } => {
let key_str = self.expr_to_string(key)?;
let vec: Vec<f32> = vector
.iter()
.map(|e| self.expr_to_f32(e))
.collect::<Result<_>>()?;
if let Some(coll) = collection {
self.vector.store_in_collection(coll, &key_str, vec)?;
} else {
self.vector.store_embedding(&key_str, vec)?;
self.bump_vector_generation();
}
Ok(QueryResult::Empty)
},
EmbedOp::Get { key } => {
let key_str = self.expr_to_string(key)?;
let vec = if let Some(coll) = collection {
self.vector.get_from_collection(coll, &key_str)?
} else {
self.vector.get_embedding(&key_str)?
};
Ok(QueryResult::Value(format!("{vec:?}")))
},
EmbedOp::Delete { key } => {
let key_str = self.expr_to_string(key)?;
let op = DestructiveOp::EmbedDelete {
key: key_str.clone(),
};
match self.protect_destructive_op(
&format!("EMBED DELETE '{key_str}'"),
op,
vec![format!("embedding key: {}", key_str)],
) {
ProtectedOpResult::Proceed => {},
ProtectedOpResult::Cancelled => {
return Err(RouterError::CheckpointError(
"Operation cancelled by user".to_string(),
));
},
}
if let Some(coll) = collection {
self.vector.delete_from_collection(coll, &key_str)?;
} else {
self.vector.delete_embedding(&key_str)?;
self.bump_vector_generation();
}
Ok(QueryResult::Count(1))
},
EmbedOp::BuildIndex => {
if self.hnsw_index.is_some() {
Ok(QueryResult::Value("HNSW index already built".to_string()))
} else {
Err(RouterError::VectorError(
"Use router.build_vector_index() to build HNSW index".to_string(),
))
}
},
EmbedOp::Batch { items } => {
let mut count = 0;
for (key_expr, vector_exprs) in items {
let key_str = self.expr_to_string(key_expr)?;
let vec: Vec<f32> = vector_exprs
.iter()
.map(|e| self.expr_to_f32(e))
.collect::<Result<_>>()?;
if let Some(coll) = collection {
self.vector.store_in_collection(coll, &key_str, vec)?;
} else {
self.vector.store_embedding(&key_str, vec)?;
}
count += 1;
}
if collection.is_none() && count > 0 {
self.bump_vector_generation();
}
Ok(QueryResult::Count(count))
},
}
}
#[allow(clippy::too_many_lines)] fn exec_similar(&self, similar: &SimilarStmt) -> Result<QueryResult> {
let top_k = similar
.limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(10);
let collection = similar.collection.as_deref();
if let Some(ref connected_to_expr) = similar.connected_to {
let query_key = match &similar.query {
SimilarQuery::Key(key) => self.expr_to_string(key)?,
SimilarQuery::Vector(_) => {
return Err(RouterError::ParseError(
"SIMILAR...CONNECTED TO requires a key, not a vector".to_string(),
));
},
};
let connected_to = self.expr_to_string(connected_to_expr)?;
let items = self.find_similar_connected(&query_key, &connected_to, top_k)?;
let results: Vec<SimilarResult> = items
.into_iter()
.map(|item| SimilarResult {
key: item.id,
score: item.score.unwrap_or(0.0),
})
.collect();
return Ok(QueryResult::Similar(results));
}
let query_vec = match &similar.query {
SimilarQuery::Key(key) => {
let key_str = self.expr_to_string(key)?;
if let Some(coll) = collection {
self.vector.get_from_collection(coll, &key_str)?
} else {
self.vector.get_embedding(&key_str)?
}
},
SimilarQuery::Vector(exprs) => exprs
.iter()
.map(|e| self.expr_to_f32(e))
.collect::<Result<_>>()?,
};
let filter = if let Some(ref where_expr) = similar.where_clause {
Some(self.expr_to_filter_condition(where_expr)?)
} else {
None
};
let metric = match similar.metric {
Some(ParsedDistanceMetric::Cosine) | None => VectorDistanceMetric::Cosine,
Some(ParsedDistanceMetric::Euclidean) => VectorDistanceMetric::Euclidean,
Some(ParsedDistanceMetric::DotProduct) => VectorDistanceMetric::DotProduct,
};
let results = match (collection, &filter) {
(Some(coll), Some(f)) => self
.vector
.search_filtered_in_collection(coll, &query_vec, top_k, f, None)?
.into_iter()
.map(|r| SimilarResult {
key: r.key,
score: r.score,
})
.collect(),
(Some(coll), None) => self
.vector
.search_in_collection(coll, &query_vec, top_k)?
.into_iter()
.map(|r| SimilarResult {
key: r.key,
score: r.score,
})
.collect(),
(None, Some(f)) => self
.vector
.search_similar_filtered(&query_vec, top_k, f, None)?
.into_iter()
.map(|r| SimilarResult {
key: r.key,
score: r.score,
})
.collect(),
(None, None) => {
if let Some((ref index, ref keys)) =
self.hnsw_index.as_ref().filter(|_| self.hnsw_is_fresh())
{
if matches!(metric, VectorDistanceMetric::Cosine) {
self.vector
.search_with_hnsw(index, keys, &query_vec, top_k)?
.into_iter()
.map(|r| SimilarResult {
key: r.key,
score: r.score,
})
.collect()
} else {
self.vector
.search_similar_with_metric(&query_vec, top_k, metric)?
.into_iter()
.map(|r| SimilarResult {
key: r.key,
score: r.score,
})
.collect()
}
} else {
self.vector
.search_similar_with_metric(&query_vec, top_k, metric)?
.into_iter()
.map(|r| SimilarResult {
key: r.key,
score: r.score,
})
.collect()
}
},
};
Ok(QueryResult::Similar(results))
}
fn exec_find(&self, find: &FindStmt) -> Result<QueryResult> {
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
let limit = find
.limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?;
let has_similar = find.similar_to.is_some();
let has_connected = find.connected_to.is_some();
if (has_similar || has_connected) && !matches!(find.pattern, FindPattern::Nodes { .. }) {
return Err(RouterError::InvalidArgument(
"SIMILAR TO and CONNECTED TO are only supported with FIND NODE".to_string(),
));
}
if has_similar || has_connected {
let FindPattern::Nodes { ref label } = find.pattern else {
unreachable!()
};
let condition = find
.where_clause
.as_ref()
.map(|expr| self.expr_to_condition(expr))
.transpose()?;
let similar_key = find
.similar_to
.as_ref()
.map(|e| self.expr_to_string(e))
.transpose()?;
let connected_key = find
.connected_to
.as_ref()
.map(|e| self.expr_to_string(e))
.transpose()?;
let effective_limit = limit.unwrap_or(100);
let label_filter = label.as_ref().map(|l| l.name.as_str());
let items = runtime
.block_on(unified.find_nodes_hybrid(
label_filter,
condition.as_ref(),
similar_key.as_deref(),
connected_key.as_deref(),
effective_limit,
))
.map_err(|e| RouterError::GraphError(e.to_string()))?;
let description = format!(
"Found {} node{}",
items.len(),
if items.len() == 1 { "" } else { "s" }
);
return Ok(QueryResult::Unified(UnifiedResult { description, items }));
}
let pattern = self.convert_find_pattern(&find.pattern);
if let Some(ref where_expr) = find.where_clause {
let condition = self.expr_to_condition(where_expr)?;
let effective_limit = limit.unwrap_or(100);
let (items, entity_type) = match &find.pattern {
FindPattern::Nodes { label } => {
let label_filter = label.as_ref().map(|l| l.name.as_str());
let items =
self.scan_find_nodes(label_filter, Some(&condition), effective_limit, 0)?;
(items, "node")
},
FindPattern::Edges { edge_type } => {
let type_filter = edge_type.as_ref().map(|t| t.name.as_str());
let items =
self.scan_find_edges(type_filter, Some(&condition), effective_limit, 0)?;
(items, "edge")
},
FindPattern::Rows { table } => {
let items =
self.scan_find_rows(&table.name, Some(&condition), effective_limit)?;
(items, "row")
},
FindPattern::Path { .. } => (Vec::new(), "path"),
};
let description = format!(
"Found {} {}{}",
items.len(),
entity_type,
if items.len() == 1 { "" } else { "s" }
);
return Ok(QueryResult::Unified(UnifiedResult { description, items }));
}
let result = runtime
.block_on(unified.find(&pattern, limit))
.map_err(|e| RouterError::GraphError(e.to_string()))?;
Ok(QueryResult::Unified(UnifiedResult {
description: result.description,
items: result.items,
}))
}
#[allow(clippy::too_many_lines)] fn exec_entity(&self, entity: &EntityStmt) -> Result<QueryResult> {
match &entity.operation {
EntityOp::Create {
key,
properties,
embedding,
} => {
let key_str = self.expr_to_string(key)?;
let fields: HashMap<String, String> = properties
.iter()
.filter_map(|p| {
self.expr_to_string(&p.value)
.ok()
.map(|v| (p.key.name.clone(), v))
})
.collect();
let emb = if let Some(vec_exprs) = embedding {
let embedding_vec: Result<Vec<f32>> =
vec_exprs.iter().map(|e| self.expr_to_f32(e)).collect();
Some(embedding_vec?)
} else {
None
};
self.create_unified_entity(&key_str, fields, emb)?;
Ok(QueryResult::Value(format!("Entity '{key_str}' created")))
},
EntityOp::Get { key } => {
let key_str = self.expr_to_string(key)?;
if let Some(ref unified) = self.unified {
let runtime =
Runtime::new().map_err(|e| RouterError::InvalidArgument(e.to_string()))?;
let item = runtime
.block_on(unified.get_entity(&key_str))
.map_err(|e| RouterError::NotFound(e.to_string()))?;
return Ok(QueryResult::Unified(UnifiedResult {
description: format!("Entity: {key_str}"),
items: vec![item],
}));
}
let mut data = HashMap::new();
data.insert("key".to_string(), key_str.clone());
if let Ok(embedding) = self.vector.get_embedding(&key_str) {
let item = UnifiedItem {
id: key_str.clone(),
source: "vector".to_string(),
data,
embedding: Some(embedding),
score: None,
};
return Ok(QueryResult::Unified(UnifiedResult {
description: format!("Entity: {key_str}"),
items: vec![item],
}));
}
Err(RouterError::NotFound(format!(
"Entity '{key_str}' not found"
)))
},
EntityOp::Connect {
from_key,
to_key,
edge_type,
} => {
let from_str = self.expr_to_string(from_key)?;
let to_str = self.expr_to_string(to_key)?;
let edge_type_str = &edge_type.name;
let edge_key = self.connect_entities(&from_str, &to_str, edge_type_str)?;
Ok(QueryResult::Value(format!(
"Connected '{from_str}' -> '{to_str}' with edge '{edge_key}'"
)))
},
EntityOp::Batch { entities } => {
#[allow(clippy::type_complexity)]
let items: Vec<(
String,
HashMap<String, String>,
Option<Vec<f32>>,
)> = entities
.iter()
.map(|e| {
let key = self.expr_to_string(&e.key)?;
let props: HashMap<String, String> = e
.properties
.iter()
.filter_map(|p| {
self.expr_to_string(&p.value)
.ok()
.map(|v| (p.key.name.clone(), v))
})
.collect();
let emb = e
.embedding
.as_ref()
.map(|v| v.iter().map(|ex| self.expr_to_f32(ex)).collect())
.transpose()?;
Ok((key, props, emb))
})
.collect::<Result<Vec<_>>>()?;
let has_embeddings = items.iter().any(|(_, _, emb)| emb.is_some());
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
let batch_result = runtime
.block_on(unified.create_entities_batch(items))
.map_err(|e| RouterError::VectorError(e.to_string()))?;
if has_embeddings {
self.bump_vector_generation();
}
Ok(QueryResult::BatchResult(BatchOperationResult {
operation: "ENTITY CREATE".to_string(),
affected_count: batch_result.count,
created_ids: None, }))
},
EntityOp::Update {
key,
properties,
embedding,
} => {
let key_str = self.expr_to_string(key)?;
let fields: HashMap<String, String> = properties
.iter()
.filter_map(|p| {
self.expr_to_string(&p.value)
.ok()
.map(|v| (p.key.name.clone(), v))
})
.collect();
let emb = if let Some(vec_exprs) = embedding {
let embedding_vec: Result<Vec<f32>> =
vec_exprs.iter().map(|e| self.expr_to_f32(e)).collect();
Some(embedding_vec?)
} else {
None
};
let has_embedding = emb.is_some();
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
runtime
.block_on(unified.update_entity(&key_str, fields, emb))
.map_err(|e| RouterError::NotFound(e.to_string()))?;
if has_embedding {
self.bump_vector_generation();
}
Ok(QueryResult::Value(format!("Entity '{key_str}' updated")))
},
EntityOp::Delete { key } => {
let key_str = self.expr_to_string(key)?;
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
runtime
.block_on(unified.delete_entity(&key_str))
.map_err(|e| RouterError::NotFound(e.to_string()))?;
self.bump_vector_generation();
Ok(QueryResult::Value(format!("Entity '{key_str}' deleted")))
},
}
}
fn scan_find_nodes(
&self,
label_filter: Option<&str>,
condition: Option<&Condition>,
limit: usize,
offset: usize,
) -> Result<Vec<UnifiedItem>> {
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
let items = runtime
.block_on(unified.find_nodes(label_filter, condition))
.map_err(|e| RouterError::GraphError(e.to_string()))?;
let items: Vec<UnifiedItem> = items.into_iter().skip(offset).take(limit).collect();
Ok(items)
}
fn scan_find_edges(
&self,
type_filter: Option<&str>,
condition: Option<&Condition>,
limit: usize,
offset: usize,
) -> Result<Vec<UnifiedItem>> {
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
let items = runtime
.block_on(unified.find_edges(type_filter, condition))
.map_err(|e| RouterError::GraphError(e.to_string()))?;
let items: Vec<UnifiedItem> = items.into_iter().skip(offset).take(limit).collect();
Ok(items)
}
fn scan_find_rows(
&self,
table: &str,
condition: Option<&Condition>,
limit: usize,
) -> Result<Vec<UnifiedItem>> {
let unified = self.require_unified()?;
let runtime = Self::create_runtime()?;
let mut items = runtime
.block_on(unified.find_rows(table, condition))
.map_err(|e| RouterError::RelationalError(e.to_string()))?;
items.truncate(limit);
Ok(items)
}
#[allow(clippy::unused_self)] fn convert_find_pattern(&self, ast_pattern: &FindPattern) -> UnifiedFindPattern {
match ast_pattern {
FindPattern::Nodes { label } => UnifiedFindPattern::Nodes {
label: label.as_ref().map(|l| l.name.clone()),
},
FindPattern::Edges { edge_type } => UnifiedFindPattern::Edges {
edge_type: edge_type.as_ref().map(|t| t.name.clone()),
},
FindPattern::Rows { table } => UnifiedFindPattern::Rows {
table: table.name.clone(),
},
FindPattern::Path { from, edge, to } => UnifiedFindPattern::Path {
from: from.as_ref().map(|f| f.name.clone()),
edge: edge.as_ref().map(|e| e.name.clone()),
to: to.as_ref().map(|t| t.name.clone()),
},
}
}
fn expr_to_condition(&self, expr: &Expr) -> Result<Condition> {
match &expr.kind {
ExprKind::Binary(left, op, right) => match op {
BinaryOp::And => {
let l = self.expr_to_condition(left)?;
let r = self.expr_to_condition(right)?;
Ok(l.and(r))
},
BinaryOp::Or => {
let l = self.expr_to_condition(left)?;
let r = self.expr_to_condition(right)?;
Ok(l.or(r))
},
BinaryOp::Eq => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_value(right)?;
Ok(Condition::Eq(col, val))
},
BinaryOp::Ne => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_value(right)?;
Ok(Condition::Ne(col, val))
},
BinaryOp::Lt => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_value(right)?;
Ok(Condition::Lt(col, val))
},
BinaryOp::Le => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_value(right)?;
Ok(Condition::Le(col, val))
},
BinaryOp::Gt => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_value(right)?;
Ok(Condition::Gt(col, val))
},
BinaryOp::Ge => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_value(right)?;
Ok(Condition::Ge(col, val))
},
_ => Err(RouterError::ParseError(format!(
"Unsupported operator in condition: {op:?}"
))),
},
_ => Err(RouterError::ParseError(
"Expected binary expression in condition".to_string(),
)),
}
}
pub fn expr_to_filter_condition(&self, expr: &Expr) -> Result<FilterCondition> {
match &expr.kind {
ExprKind::Binary(left, op, right) => match op {
BinaryOp::And => {
let l = self.expr_to_filter_condition(left)?;
let r = self.expr_to_filter_condition(right)?;
Ok(l.and(r))
},
BinaryOp::Or => {
let l = self.expr_to_filter_condition(left)?;
let r = self.expr_to_filter_condition(right)?;
Ok(l.or(r))
},
BinaryOp::Eq => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_filter_value(right)?;
Ok(FilterCondition::Eq(col, val))
},
BinaryOp::Ne => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_filter_value(right)?;
Ok(FilterCondition::Ne(col, val))
},
BinaryOp::Lt => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_filter_value(right)?;
Ok(FilterCondition::Lt(col, val))
},
BinaryOp::Le => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_filter_value(right)?;
Ok(FilterCondition::Le(col, val))
},
BinaryOp::Gt => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_filter_value(right)?;
Ok(FilterCondition::Gt(col, val))
},
BinaryOp::Ge => {
let col = self.expr_to_column_name(left)?;
let val = self.expr_to_filter_value(right)?;
Ok(FilterCondition::Ge(col, val))
},
_ => Err(RouterError::ParseError(format!(
"Unsupported operator in filter condition: {op:?}"
))),
},
_ => Err(RouterError::ParseError(
"Expected binary expression in filter condition".to_string(),
)),
}
}
pub fn expr_to_filter_value(&self, expr: &Expr) -> Result<FilterValue> {
match &expr.kind {
ExprKind::Literal(lit) => match lit {
Literal::Null => Ok(FilterValue::String("null".to_string())),
Literal::Boolean(b) => Ok(FilterValue::Bool(*b)),
Literal::Integer(i) => Ok(FilterValue::Int(*i)),
Literal::Float(f) => Ok(FilterValue::Float(*f)),
Literal::String(s) => Ok(FilterValue::String(s.clone())),
},
ExprKind::Ident(ident) => Ok(FilterValue::String(ident.name.clone())),
_ => Err(RouterError::ParseError(format!(
"Cannot convert expression to filter value: {:?}",
expr.kind
))),
}
}
#[allow(clippy::unused_self)] fn expr_to_value(&self, expr: &Expr) -> Result<Value> {
match &expr.kind {
ExprKind::Literal(lit) => match lit {
Literal::Null => Ok(Value::Null),
Literal::Boolean(b) => Ok(Value::Bool(*b)),
Literal::Integer(i) => Ok(Value::Int(*i)),
Literal::Float(f) => Ok(Value::Float(*f)),
Literal::String(s) => Ok(Value::String(s.clone())),
},
ExprKind::Ident(ident) => Ok(Value::String(ident.name.clone())),
_ => Err(RouterError::ParseError(format!(
"Cannot convert expression to value: {:?}",
expr.kind
))),
}
}
pub fn expr_to_column_name(&self, expr: &Expr) -> Result<String> {
match &expr.kind {
ExprKind::Ident(ident) => Ok(ident.name.clone()),
ExprKind::Qualified(_, name) => Ok(name.name.clone()),
_ => Err(RouterError::ParseError("Expected column name".to_string())),
}
}
#[allow(clippy::unused_self)] #[allow(clippy::cast_sign_loss)] fn expr_to_u64(&self, expr: &Expr) -> Result<u64> {
match &expr.kind {
ExprKind::Literal(Literal::Integer(i)) if *i >= 0 => Ok(*i as u64),
_ => Err(RouterError::InvalidArgument(
"Expected positive integer".to_string(),
)),
}
}
#[allow(clippy::unused_self)] #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_precision_loss)] fn expr_to_f32(&self, expr: &Expr) -> Result<f32> {
match &expr.kind {
ExprKind::Literal(Literal::Float(f)) => Ok(*f as f32),
ExprKind::Literal(Literal::Integer(i)) => Ok(*i as f32),
ExprKind::Unary(parser::UnaryOp::Neg, inner) => match &inner.kind {
ExprKind::Literal(Literal::Float(f)) => Ok(-(*f as f32)),
ExprKind::Literal(Literal::Integer(i)) => Ok(-(*i as f32)),
_ => Err(RouterError::InvalidArgument("Expected number".to_string())),
},
_ => Err(RouterError::InvalidArgument("Expected number".to_string())),
}
}
#[allow(clippy::unused_self)] #[allow(clippy::cast_precision_loss)] fn expr_to_f64(&self, expr: &Expr) -> Result<f64> {
match &expr.kind {
ExprKind::Literal(Literal::Float(f)) => Ok(*f),
ExprKind::Literal(Literal::Integer(i)) => Ok(*i as f64),
ExprKind::Unary(parser::UnaryOp::Neg, inner) => match &inner.kind {
ExprKind::Literal(Literal::Float(f)) => Ok(-*f),
ExprKind::Literal(Literal::Integer(i)) => Ok(-(*i as f64)),
_ => Err(RouterError::InvalidArgument("Expected number".to_string())),
},
_ => Err(RouterError::InvalidArgument("Expected number".to_string())),
}
}
#[allow(clippy::unused_self)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_possible_truncation)] fn expr_to_usize(&self, expr: &Expr) -> Result<usize> {
match &expr.kind {
ExprKind::Literal(Literal::Integer(i)) if *i >= 0 => Ok(*i as usize),
_ => Err(RouterError::InvalidArgument(
"Expected positive integer".to_string(),
)),
}
}
#[allow(clippy::unused_self)] fn expr_to_string(&self, expr: &Expr) -> Result<String> {
match &expr.kind {
ExprKind::Literal(Literal::String(s)) => Ok(s.clone()),
ExprKind::Ident(ident) => Ok(ident.name.clone()),
_ => Err(RouterError::InvalidArgument("Expected string".to_string())),
}
}
fn exec_spatial(&self, spatial_stmt: &SpatialStmt) -> Result<QueryResult> {
match &spatial_stmt.op {
SpatialOp::Insert {
key,
x,
y,
width,
height,
} => {
let key_str = self.expr_to_string(key)?;
let x_val = self.expr_to_f32(x)?;
let y_val = self.expr_to_f32(y)?;
let w_val = self.expr_to_f32(width)?;
let h_val = self.expr_to_f32(height)?;
let bounds = tensor_spatial::BoundingBox::new(x_val, y_val, w_val, h_val)
.map_err(|e| RouterError::InvalidArgument(e.to_string()))?;
let entry = tensor_spatial::SpatialEntry {
bounds,
data: key_str,
};
self.spatial.write().insert(entry);
Ok(QueryResult::Empty)
},
SpatialOp::WithinRadius {
x,
y,
radius,
limit,
} => {
let cx = self.expr_to_f32(x)?;
let cy = self.expr_to_f32(y)?;
let r = self.expr_to_f32(radius)?;
let max_results = limit.as_ref().map(|e| self.expr_to_usize(e)).transpose()?;
if !r.is_finite() || r < 0.0 {
return Err(RouterError::InvalidArgument(
"invalid radius: must be non-negative and finite".to_string(),
));
}
let mut results: Vec<SpatialResult> = self
.spatial
.read()
.query_within_radius_with_distances(cx, cy, r)
.into_iter()
.map(|(e, dist)| SpatialResult {
key: e.data.clone(),
distance: dist,
x: e.bounds.x(),
y: e.bounds.y(),
width: e.bounds.width(),
height: e.bounds.height(),
})
.collect();
if let Some(max) = max_results {
results.truncate(max);
}
Ok(QueryResult::Spatial(results))
},
SpatialOp::Delete {
key,
x,
y,
width,
height,
} => {
let key_str = self.expr_to_string(key)?;
let x_val = self.expr_to_f32(x)?;
let y_val = self.expr_to_f32(y)?;
let w_val = self.expr_to_f32(width)?;
let h_val = self.expr_to_f32(height)?;
let bounds = tensor_spatial::BoundingBox::new(x_val, y_val, w_val, h_val)
.map_err(|e| RouterError::InvalidArgument(e.to_string()))?;
self.spatial
.write()
.remove(bounds, |e| e.data == key_str && e.bounds == bounds)
.map_err(|e| RouterError::NotFound(e.to_string()))?;
Ok(QueryResult::Empty)
},
SpatialOp::Nearest { x, y, limit } => self.exec_spatial_nearest(x, y, limit.as_ref()),
SpatialOp::Count => {
let count = self.spatial.read().len();
Ok(QueryResult::Count(count))
},
}
}
fn exec_spatial_nearest(
&self,
x: &Expr,
y: &Expr,
limit: Option<&Expr>,
) -> Result<QueryResult> {
let cx = self.expr_to_f32(x)?;
let cy = self.expr_to_f32(y)?;
let k = limit
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(1);
let results: Vec<SpatialResult> = self
.spatial
.read()
.query_nearest_by_centroid(cx, cy, k)
.into_iter()
.map(|e| SpatialResult {
key: e.data.clone(),
distance: e.bounds.center_dist_sq(cx, cy).sqrt(),
x: e.bounds.x(),
y: e.bounds.y(),
width: e.bounds.width(),
height: e.bounds.height(),
})
.collect();
Ok(QueryResult::Spatial(results))
}
fn extract_join_columns(&self, condition: &JoinCondition) -> Result<(String, String)> {
match condition {
JoinCondition::On(expr) => match &expr.kind {
ExprKind::Binary(left, BinaryOp::Eq, right) => {
let left_col = self.extract_column_from_expr(left)?;
let right_col = self.extract_column_from_expr(right)?;
Ok((left_col, right_col))
},
_ => Err(RouterError::ParseError(
"JOIN ON condition must be an equality comparison (a.col = b.col)".to_string(),
)),
},
JoinCondition::Using(cols) => {
if cols.len() == 1 {
let col = cols[0].name.clone();
Ok((col.clone(), col))
} else {
Err(RouterError::ParseError(
"JOIN USING with multiple columns not yet supported".to_string(),
))
}
},
}
}
#[allow(clippy::unused_self)] fn extract_column_from_expr(&self, expr: &Expr) -> Result<String> {
match &expr.kind {
ExprKind::Ident(ident) => Ok(ident.name.clone()),
ExprKind::Qualified(_, col) => Ok(col.name.clone()),
_ => Err(RouterError::ParseError(
"Expected column reference in JOIN condition".to_string(),
)),
}
}
#[allow(clippy::unused_self)] #[allow(clippy::cast_possible_wrap)] fn merge_rows(
&self,
row_a: Option<&Row>,
row_b: Option<&Row>,
table_a: &str,
table_b: &str,
) -> Row {
let mut values = Vec::new();
if let Some(r) = row_a {
values.push((format!("{table_a}._id"), Value::Int(r.id as i64)));
for (col, val) in &r.values {
values.push((format!("{table_a}.{col}"), val.clone()));
}
}
if let Some(r) = row_b {
values.push((format!("{table_b}._id"), Value::Int(r.id as i64)));
for (col, val) in &r.values {
values.push((format!("{table_b}.{col}"), val.clone()));
}
}
Row {
id: row_a
.map(|r| r.id)
.or_else(|| row_b.map(|r| r.id))
.unwrap_or(0),
values,
}
}
#[allow(clippy::unused_self)] fn properties_to_map(&self, properties: &[Property]) -> Result<HashMap<String, PropertyValue>> {
let mut map = HashMap::new();
for prop in properties {
let value = match &prop.value.kind {
ExprKind::Literal(Literal::Null) => PropertyValue::Null,
ExprKind::Literal(Literal::Boolean(b)) => PropertyValue::Bool(*b),
ExprKind::Literal(Literal::Integer(i)) => PropertyValue::Int(*i),
ExprKind::Literal(Literal::Float(f)) => PropertyValue::Float(*f),
ExprKind::Literal(Literal::String(s)) => PropertyValue::String(s.clone()),
ExprKind::Ident(ident) => PropertyValue::String(ident.name.clone()),
_ => {
return Err(RouterError::InvalidArgument(format!(
"Invalid property value: {:?}",
prop.value.kind
)))
},
};
map.insert(prop.key.name.clone(), value);
}
Ok(map)
}
#[allow(clippy::unused_self)] fn data_type_to_column_type(
&self,
dt: &parser::DataType,
) -> Result<relational_engine::ColumnType> {
use parser::DataType;
match dt {
DataType::Int | DataType::Integer | DataType::Bigint | DataType::Smallint => {
Ok(relational_engine::ColumnType::Int)
},
DataType::Float
| DataType::Double
| DataType::Real
| DataType::Decimal(_, _)
| DataType::Numeric(_, _) => Ok(relational_engine::ColumnType::Float),
DataType::Varchar(_)
| DataType::Char(_)
| DataType::Text
| DataType::Date
| DataType::Time
| DataType::Timestamp
| DataType::Blob => Ok(relational_engine::ColumnType::String),
DataType::Boolean => Ok(relational_engine::ColumnType::Bool),
DataType::Custom(name) => match name.to_uppercase().as_str() {
"STRING" => Ok(relational_engine::ColumnType::String),
"BOOL" => Ok(relational_engine::ColumnType::Bool),
_ => Err(RouterError::ParseError(format!(
"Unsupported data type: {name}"
))),
},
}
}
fn property_to_string(prop: &PropertyValue) -> String {
match prop {
PropertyValue::Null => "null".to_string(),
PropertyValue::Int(i) => i.to_string(),
PropertyValue::Float(f) => f.to_string(),
PropertyValue::String(s) => s.clone(),
PropertyValue::Bool(b) => b.to_string(),
PropertyValue::DateTime(ts) => ts.to_string(),
PropertyValue::List(items) => format!(
"[{}]",
items
.iter()
.map(Self::property_to_string)
.collect::<Vec<_>>()
.join(", ")
),
PropertyValue::Map(map) => format!(
"{{{}}}",
map.iter()
.map(|(k, v)| format!("{}: {}", k, Self::property_to_string(v)))
.collect::<Vec<_>>()
.join(", ")
),
PropertyValue::Bytes(bytes) => format!("<{} bytes>", bytes.len()),
PropertyValue::Point { lat, lon } => format!("POINT({lat}, {lon})"),
}
}
pub async fn execute_parsed_async(&self, command: &str) -> Result<QueryResult> {
let stmt = parser::parse(command)
.map_err(|e| RouterError::ParseError(e.format_with_source(command)))?;
if Self::is_cacheable_statement(&stmt) {
if let Some(cached) = self.try_cache_get(command) {
return Ok(cached);
}
}
let result = self.execute_statement_async(&stmt).await?;
if Self::is_cacheable_statement(&stmt) {
self.try_cache_put(command, &result);
}
if Self::is_write_statement(&stmt) {
self.invalidate_cache_on_write();
}
Ok(result)
}
pub async fn execute_statement_async(&self, stmt: &Statement) -> Result<QueryResult> {
match &stmt.kind {
StatementKind::Blob(blob) => self.exec_blob_async(blob).await,
StatementKind::Blobs(blobs) => self.exec_blobs_async(blobs).await,
StatementKind::Checkpoint(cp) => self.exec_checkpoint(cp),
StatementKind::Rollback(rb) => self.exec_rollback(rb),
StatementKind::Checkpoints(cps) => self.exec_checkpoints(cps),
_ => self.execute_statement(stmt),
}
}
#[allow(clippy::too_many_lines)] #[allow(clippy::significant_drop_tightening)] async fn exec_blob_async(&self, stmt: &BlobStmt) -> Result<QueryResult> {
if matches!(stmt.operation, BlobOp::Init) {
if self.blob.is_some() {
return Ok(QueryResult::Value(
"Blob store already initialized".to_string(),
));
}
return Err(RouterError::BlobError(
"Use router.init_blob() to initialize blob storage".to_string(),
));
}
let blob = self
.blob
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob store not initialized".to_string()))?;
match &stmt.operation {
BlobOp::Init => unreachable!(), BlobOp::Put {
filename,
data,
from_path,
options,
} => {
let filename_str = self.eval_string_expr(filename)?;
let put_options = self.blob_options_to_put_options(options)?;
let blob_data = if let Some(data_expr) = data {
self.expr_to_bytes(data_expr)?
} else if let Some(path_expr) = from_path {
let path = self.eval_string_expr(path_expr)?;
tokio::fs::read(&path)
.await
.map_err(|e| RouterError::BlobError(format!("Failed to read file: {e}")))?
} else {
return Err(RouterError::MissingArgument(
"PUT requires either DATA or FROM path".to_string(),
));
};
let blob_guard = blob.lock().await;
let artifact_id = blob_guard
.put(&filename_str, &blob_data, put_options)
.await?;
Ok(QueryResult::Value(artifact_id))
},
BlobOp::Get {
artifact_id,
to_path,
} => {
let id = self.eval_string_expr(artifact_id)?;
let blob_guard = blob.lock().await;
let data = blob_guard.get(&id).await?;
if let Some(path_expr) = to_path {
let path = self.eval_string_expr(path_expr)?;
tokio::fs::write(&path, &data).await.map_err(|e| {
RouterError::BlobError(format!("Failed to write file: {e}"))
})?;
Ok(QueryResult::Value(format!(
"Written {} bytes to {path}",
data.len()
)))
} else {
Ok(QueryResult::Blob(data))
}
},
BlobOp::Delete { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let blob_guard = blob.lock().await;
blob_guard.delete(&id).await?;
Ok(QueryResult::Empty)
},
BlobOp::Info { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let blob_guard = blob.lock().await;
let meta = blob_guard.metadata(&id).await?;
Ok(QueryResult::ArtifactInfo(ArtifactInfoResult {
id: meta.id,
filename: meta.filename,
content_type: meta.content_type,
size: meta.size,
checksum: meta.checksum,
chunk_count: meta.chunk_count,
created: meta.created,
modified: meta.modified,
created_by: meta.created_by,
tags: meta.tags,
linked_to: meta.linked_to,
custom: meta.custom,
}))
},
BlobOp::Link {
artifact_id,
entity,
} => {
let id = self.eval_string_expr(artifact_id)?;
let entity_str = self.eval_string_expr(entity)?;
let blob_guard = blob.lock().await;
blob_guard.link(&id, &entity_str).await?;
Ok(QueryResult::Empty)
},
BlobOp::Unlink {
artifact_id,
entity,
} => {
let id = self.eval_string_expr(artifact_id)?;
let entity_str = self.eval_string_expr(entity)?;
let blob_guard = blob.lock().await;
blob_guard.unlink(&id, &entity_str).await?;
Ok(QueryResult::Empty)
},
BlobOp::Links { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let blob_guard = blob.lock().await;
let links = blob_guard.links(&id).await?;
Ok(QueryResult::ArtifactList(links))
},
BlobOp::Tag { artifact_id, tag } => {
let id = self.eval_string_expr(artifact_id)?;
let tag_str = self.eval_string_expr(tag)?;
let blob_guard = blob.lock().await;
blob_guard.tag(&id, &tag_str).await?;
Ok(QueryResult::Empty)
},
BlobOp::Untag { artifact_id, tag } => {
let id = self.eval_string_expr(artifact_id)?;
let tag_str = self.eval_string_expr(tag)?;
let blob_guard = blob.lock().await;
blob_guard.untag(&id, &tag_str).await?;
Ok(QueryResult::Empty)
},
BlobOp::Verify { artifact_id } => {
let id = self.eval_string_expr(artifact_id)?;
let blob_guard = blob.lock().await;
let valid = blob_guard.verify(&id)?;
Ok(QueryResult::Value(if valid {
"OK".to_string()
} else {
"INVALID".to_string()
}))
},
BlobOp::Gc { full } => {
let blob_guard = blob.lock().await;
let stats = if *full {
blob_guard.full_gc().await?
} else {
blob_guard.gc().await?
};
Ok(QueryResult::Value(format!(
"Deleted {} chunks, freed {} bytes",
stats.deleted, stats.freed_bytes
)))
},
BlobOp::Repair => {
let blob_guard = blob.lock().await;
let stats = blob_guard.repair()?;
Ok(QueryResult::Value(format!(
"Fixed {} refs, deleted {} orphans",
stats.refs_fixed, stats.orphans_deleted
)))
},
BlobOp::Stats => {
let blob_guard = blob.lock().await;
let stats = blob_guard.stats().await?;
Ok(QueryResult::BlobStats(BlobStatsResult {
artifact_count: stats.artifact_count,
chunk_count: stats.chunk_count,
total_bytes: stats.total_bytes,
unique_bytes: stats.unique_bytes,
dedup_ratio: stats.dedup_ratio,
orphaned_chunks: stats.orphaned_chunks,
}))
},
BlobOp::MetaSet {
artifact_id,
key,
value,
} => {
let id = self.eval_string_expr(artifact_id)?;
let key_str = self.eval_string_expr(key)?;
let value_str = self.eval_string_expr(value)?;
let blob_guard = blob.lock().await;
blob_guard.set_meta(&id, &key_str, &value_str).await?;
Ok(QueryResult::Empty)
},
BlobOp::MetaGet { artifact_id, key } => {
let id = self.eval_string_expr(artifact_id)?;
let key_str = self.eval_string_expr(key)?;
let blob_guard = blob.lock().await;
let value = blob_guard.get_meta(&id, &key_str).await?;
Ok(QueryResult::Value(
value.unwrap_or_else(|| "(not found)".to_string()),
))
},
}
}
#[allow(clippy::significant_drop_tightening)] async fn exec_blobs_async(&self, stmt: &BlobsStmt) -> Result<QueryResult> {
let blob = self
.blob
.as_ref()
.ok_or_else(|| RouterError::BlobError("Blob store not initialized".to_string()))?;
let blob_guard = blob.lock().await;
match &stmt.operation {
BlobsOp::List { pattern } => {
let prefix = pattern
.as_ref()
.map(|p| self.eval_string_expr(p))
.transpose()?;
let ids = blob_guard.list(prefix.as_deref()).await?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::For { entity } => {
let entity_str = self.eval_string_expr(entity)?;
let ids = blob_guard.artifacts_for(&entity_str).await?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::ByTag { tag } => {
let tag_str = self.eval_string_expr(tag)?;
let ids = blob_guard.by_tag(&tag_str).await?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::ByType { content_type } => {
let ct = self.eval_string_expr(content_type)?;
let ids = blob_guard.by_content_type(&ct).await?;
Ok(QueryResult::ArtifactList(ids))
},
BlobsOp::Similar { artifact_id, limit } => {
let id = self.eval_string_expr(artifact_id)?;
let k = limit
.as_ref()
.map(|e| self.expr_to_usize(e))
.transpose()?
.unwrap_or(10);
let similar = blob_guard.similar(&id, k).await?;
Ok(QueryResult::Similar(
similar
.into_iter()
.map(|s| SimilarResult {
key: s.id,
score: s.similarity,
})
.collect(),
))
},
}
}
pub async fn embed_batch_parallel(&self, items: Vec<(String, Vec<f32>)>) -> Result<usize> {
let unified = self.require_unified()?;
let count = unified
.embed_batch(items)
.await
.map(|result| result.count)
.map_err(|e| RouterError::VectorError(e.to_string()))?;
if count > 0 {
self.bump_vector_generation();
}
Ok(count)
}
pub async fn find_similar_connected_async(
&self,
query_key: &str,
connected_to: &str,
top_k: usize,
) -> Result<Vec<UnifiedItem>> {
let unified = self.require_unified()?;
let hnsw_results = if let Some((ref index, ref keys)) =
self.hnsw_index.as_ref().filter(|_| self.hnsw_is_fresh())
{
let query_embedding = self
.vector
.get_entity_embedding(query_key)
.map_err(|e| RouterError::VectorError(e.to_string()))?;
Some(
self.vector
.search_with_hnsw(index, keys, &query_embedding, top_k.saturating_mul(8))
.map_err(|e| RouterError::VectorError(e.to_string()))?,
)
} else {
None
};
unified
.find_similar_connected_with_hnsw(query_key, connected_to, top_k, hnsw_results)
.await
.map_err(Into::into)
}
pub async fn find_neighbors_by_similarity_async(
&self,
entity_key: &str,
query: &[f32],
top_k: usize,
) -> Result<Vec<UnifiedItem>> {
let unified = self.require_unified()?;
unified
.find_neighbors_by_similarity(entity_key, query, top_k)
.await
.map_err(Into::into)
}
pub fn runtime(&self) -> Option<&Runtime> {
self.blob_runtime.as_deref()
}
pub fn block_on<F: std::future::Future>(&self, future: F) -> Result<F::Output> {
let runtime = self.blob_runtime.as_ref().ok_or_else(|| {
RouterError::BlobError("Runtime not initialized. Call init_blob() first.".to_string())
})?;
Ok(runtime.block_on(future))
}
}
impl Default for QueryRouter {
fn default() -> Self {
Self::new()
}
}
impl QueryRouter {
pub fn execute_for_cluster(&self, query: &str) -> std::result::Result<Vec<u8>, String> {
let result = self.execute_parsed(query).map_err(|e| e.to_string())?;
bitcode::serialize(&result).map_err(|e| format!("Serialization error: {e}"))
}
}
impl QueryExecutor for QueryRouter {
fn execute(&self, query: &str) -> std::result::Result<Vec<u8>, String> {
self.execute_for_cluster(query)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tensor_checkpoint::OperationPreview;
fn add_test_edge(graph: &GraphEngine, from_key: &str, to_key: &str, edge_type: &str) {
let get_or_create = |key: &str| -> u64 {
if let Ok(nodes) =
graph.find_nodes_by_property("entity_key", &PropertyValue::String(key.to_string()))
{
if let Some(node) = nodes.first() {
return node.id;
}
}
let mut props = HashMap::new();
props.insert(
"entity_key".to_string(),
PropertyValue::String(key.to_string()),
);
graph.create_node("TestEntity", props).unwrap_or(0)
};
let from_node = get_or_create(from_key);
let to_node = get_or_create(to_key);
graph
.create_edge(from_node, to_node, edge_type, HashMap::new(), true)
.ok();
}
fn unwrap_qr_artifactinfo(result: QueryResult) -> ArtifactInfoResult {
match result {
QueryResult::ArtifactInfo(v) => v,
_ => panic!("expected ArtifactInfo"),
}
}
fn unwrap_qr_artifactlist(result: QueryResult) -> Vec<String> {
match result {
QueryResult::ArtifactList(v) => v,
_ => panic!("expected ArtifactList"),
}
}
fn unwrap_qr_blob(result: QueryResult) -> Vec<u8> {
match result {
QueryResult::Blob(v) => v,
_ => panic!("expected Blob"),
}
}
fn unwrap_qr_blobstats(result: QueryResult) -> BlobStatsResult {
match result {
QueryResult::BlobStats(v) => v,
_ => panic!("expected BlobStats"),
}
}
fn unwrap_qr_checkpointlist(result: QueryResult) -> Vec<CheckpointInfo> {
match result {
QueryResult::CheckpointList(v) => v,
_ => panic!("expected CheckpointList"),
}
}
fn unwrap_qr_constraints(result: QueryResult) -> Vec<ConstraintInfo> {
match result {
QueryResult::Constraints(v) => v,
_ => panic!("expected Constraints"),
}
}
fn unwrap_qr_edges(result: QueryResult) -> Vec<EdgeResult> {
match result {
QueryResult::Edges(v) => v,
_ => panic!("expected Edges"),
}
}
fn unwrap_qr_nodes(result: QueryResult) -> Vec<NodeResult> {
match result {
QueryResult::Nodes(v) => v,
_ => panic!("expected Nodes"),
}
}
fn unwrap_qr_rows(result: QueryResult) -> Vec<Row> {
match result {
QueryResult::Rows(v) => v,
_ => panic!("expected Rows"),
}
}
fn unwrap_qr_similar(result: QueryResult) -> Vec<SimilarResult> {
match result {
QueryResult::Similar(v) => v,
_ => panic!("expected Similar"),
}
}
fn unwrap_qr_unified(result: QueryResult) -> UnifiedResult {
match result {
QueryResult::Unified(v) => v,
_ => panic!("expected Unified"),
}
}
fn unwrap_qr_value(result: QueryResult) -> String {
match result {
QueryResult::Value(v) => v,
_ => panic!("expected Value"),
}
}
fn get_neighbors_out(graph: &GraphEngine, entity_key: &str) -> Vec<String> {
let node_id = graph
.find_nodes_by_property("entity_key", &PropertyValue::String(entity_key.to_string()))
.ok()
.and_then(|nodes| nodes.first().map(|n| n.id));
let Some(id) = node_id else {
return Vec::new();
};
let mut neighbors = Vec::new();
if let Ok(edges) = graph.edges_of(id, Direction::Outgoing) {
for edge in edges {
let target_id = if edge.from == id { edge.to } else { edge.from };
if let Ok(target_node) = graph.get_node(target_id) {
if let Some(PropertyValue::String(key)) =
target_node.properties.get("entity_key")
{
neighbors.push(key.clone());
}
}
}
}
neighbors
}
fn entity_has_edges(graph: &GraphEngine, entity_key: &str) -> bool {
let node_id = graph
.find_nodes_by_property("entity_key", &PropertyValue::String(entity_key.to_string()))
.ok()
.and_then(|nodes| nodes.first().map(|n| n.id));
let Some(id) = node_id else {
return false;
};
graph
.edges_of(id, Direction::Both)
.is_ok_and(|edges| !edges.is_empty())
}
#[test]
fn routes_select_to_relational() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE users (name string, age int)")
.unwrap();
router
.execute("INSERT INTO users (name, age) VALUES ('Alice', 30)")
.unwrap();
let result = router.execute("SELECT * FROM users").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
},
_ => panic!("Expected Rows result"),
}
}
#[test]
fn routes_node_to_graph() {
let router = QueryRouter::new();
let result = router
.execute("NODE CREATE person { name: 'Bob' }")
.unwrap();
match result {
QueryResult::Ids(ids) => {
assert_eq!(ids.len(), 1);
},
_ => panic!("Expected Ids result"),
}
}
#[test]
fn routes_embed_to_vector() {
let router = QueryRouter::new();
let result = router.execute("EMBED doc1 [1.0, 0.0, 0.0]").unwrap();
match result {
QueryResult::Empty => {},
_ => panic!("Expected Empty result"),
}
assert!(router.vector().exists("doc1"));
}
#[test]
fn routes_similar_to_vector() {
let router = QueryRouter::new();
router.execute("EMBED doc1 [1.0, 0.0, 0.0]").unwrap();
router.execute("EMBED doc2 [0.0, 1.0, 0.0]").unwrap();
router.execute("EMBED doc3 [0.9, 0.1, 0.0]").unwrap();
let result = router.execute("SIMILAR doc1 TOP 2").unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 2);
assert_eq!(results[0].key, "doc1"); },
_ => panic!("Expected Similar result"),
}
}
#[test]
fn handles_unified_query_find_nodes() {
let router = QueryRouter::new();
router
.execute("NODE CREATE post { title: 'Post 1' }")
.unwrap();
router
.execute("NODE CREATE post { title: 'Post 2' }")
.unwrap();
router
.execute("NODE CREATE post { title: 'Post 3' }")
.unwrap();
let result = router.execute("FIND NODES post").unwrap();
match result {
QueryResult::Unified(unified) => {
assert!(unified.description.contains("node"));
assert_eq!(unified.items.len(), 3);
},
_ => panic!("Expected Unified result"),
}
}
#[test]
fn handles_unified_query_connected() {
let router = QueryRouter::new();
let user_id = match router
.execute("NODE CREATE user { name: 'Alice' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let post_id = match router
.execute("NODE CREATE post { title: 'Hello' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!(
"EDGE CREATE {} -> {} : authored",
user_id, post_id
))
.unwrap();
router
.execute("EMBED STORE 'post' [1.0, 0.0, 0.0]")
.unwrap();
let result = router.execute("FIND NODES post").unwrap();
match result {
QueryResult::Unified(_) => {},
_ => panic!("Expected Unified result"),
}
}
#[test]
fn returns_error_for_malformed_command() {
let router = QueryRouter::new();
let result = router.execute("");
assert!(matches!(result, Err(RouterError::ParseError(_))));
let result = router.execute(" ");
assert!(matches!(result, Err(RouterError::ParseError(_))));
}
#[test]
fn returns_error_for_unknown_command() {
let router = QueryRouter::new();
let result = router.execute("UNKNOWN something");
assert!(matches!(result, Err(RouterError::UnknownCommand(_))));
}
#[test]
fn returns_error_for_missing_arguments() {
let router = QueryRouter::new();
let result = router.execute("SELECT");
assert!(matches!(result, Err(RouterError::ParseError(_))));
let result = router.execute("NODE");
assert!(matches!(result, Err(RouterError::ParseError(_))));
let result = router.execute("EMBED");
assert!(matches!(result, Err(RouterError::ParseError(_))));
}
#[test]
fn does_not_crash_on_unexpected_input() {
let router = QueryRouter::new();
let inputs = [
"SELECT * FROM FROM WHERE",
"INSERT INTO VALUES",
"NODE CREATE",
"EDGE 123 -> 456",
"SIMILAR [not, valid, floats]",
"FIND something WITH random KEYWORDS",
";;;",
"SELECT * FROM users; DROP TABLE users;--",
"SELECT * FROM users WHERE name = 'O'Brien'",
"\n\t\r",
];
for input in inputs {
let _ = router.execute(input);
}
}
#[test]
fn handles_table_not_found() {
let router = QueryRouter::new();
let result = router.execute("SELECT * FROM nonexistent");
assert!(matches!(result, Err(RouterError::RelationalError(_))));
}
#[test]
fn handles_node_not_found() {
let router = QueryRouter::new();
let result = router.execute("NODE GET 99999");
assert!(matches!(result, Err(RouterError::GraphError(_))));
}
#[test]
fn handles_embedding_not_found() {
let router = QueryRouter::new();
let result = router.execute("SIMILAR nonexistent TOP 5");
assert!(matches!(result, Err(RouterError::VectorError(_))));
}
#[test]
fn create_table_and_insert() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE products (name string, price float)")
.unwrap();
router
.execute("INSERT INTO products (name, price) VALUES ('Widget', 9.99)")
.unwrap();
let result = router.execute("SELECT * FROM products").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("name"), Some(&Value::String("Widget".into())));
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn select_with_where() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE items (name string, qty int)")
.unwrap();
router
.execute("INSERT INTO items (name, qty) VALUES ('A', 10)")
.unwrap();
router
.execute("INSERT INTO items (name, qty) VALUES ('B', 20)")
.unwrap();
router
.execute("INSERT INTO items (name, qty) VALUES ('C', 30)")
.unwrap();
let result = router
.execute("SELECT * FROM items WHERE qty > 15")
.unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 2);
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn update_rows() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE counters (name string, value int)")
.unwrap();
router
.execute("INSERT INTO counters (name, value) VALUES ('hits', 0)")
.unwrap();
let result = router
.execute("UPDATE counters SET value=100 WHERE name=\"hits\"")
.unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 1),
_ => panic!("Expected Count"),
}
}
#[test]
fn delete_rows() {
let router = QueryRouter::new();
router.execute("CREATE TABLE temp (id int)").unwrap();
router.execute("INSERT INTO temp (id) VALUES (1)").unwrap();
router.execute("INSERT INTO temp (id) VALUES (2)").unwrap();
let result = router.execute("DELETE FROM temp WHERE id=1").unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 1),
_ => panic!("Expected Count"),
}
}
#[test]
fn create_and_drop_index() {
let router = QueryRouter::new();
router.execute("CREATE TABLE indexed (col int)").unwrap();
router
.execute("CREATE INDEX idx_col ON indexed(col)")
.unwrap();
assert!(router.relational().has_index("indexed", "col"));
router.execute("DROP INDEX ON indexed(col)").unwrap();
assert!(!router.relational().has_index("indexed", "col"));
}
#[test]
fn drop_table() {
let router = QueryRouter::new();
router.execute("CREATE TABLE todrop (x int)").unwrap();
assert!(router.relational().table_exists("todrop"));
router.execute("DROP TABLE todrop").unwrap();
assert!(!router.relational().table_exists("todrop"));
}
#[test]
fn node_create_get_delete() {
let router = QueryRouter::new();
let id = match router
.execute("NODE CREATE person { name: 'Test' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("NODE GET {}", id)).unwrap();
match result {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].label, "person");
},
_ => panic!("Expected Nodes"),
}
router.execute(&format!("NODE DELETE {}", id)).unwrap();
}
#[test]
fn edge_create_and_get() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let edge_id = match router
.execute(&format!("EDGE CREATE {} -> {} : connects", n1, n2))
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("EDGE GET {}", edge_id)).unwrap();
match result {
QueryResult::Edges(edges) => {
assert_eq!(edges.len(), 1);
assert_eq!(edges[0].label, "connects");
},
_ => panic!("Expected Edges"),
}
}
#[test]
fn neighbors_query() {
let router = QueryRouter::new();
let center = match router.execute("NODE CREATE center").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let leaf1 = match router.execute("NODE CREATE leaf1").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let leaf2 = match router.execute("NODE CREATE leaf2").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", center, leaf1))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {}", center, leaf2))
.unwrap();
let result = router
.execute(&format!("NEIGHBORS {} OUTGOING", center))
.unwrap();
match result {
QueryResult::Ids(ids) => {
assert_eq!(ids.len(), 2);
},
_ => panic!("Expected Ids"),
}
}
#[test]
fn path_query() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let c = match router.execute("NODE CREATE c").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", a, b))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {}", b, c))
.unwrap();
let result = router.execute(&format!("PATH {} -> {}", a, c)).unwrap();
match result {
QueryResult::Path(path) => {
assert_eq!(path.len(), 3);
assert_eq!(path[0], a);
assert_eq!(path[2], c);
},
_ => panic!("Expected Path"),
}
}
#[test]
fn embed_and_similar_inline() {
let router = QueryRouter::new();
router.execute("EMBED v1 [1.0, 0.0]").unwrap();
router.execute("EMBED v2 [0.0, 1.0]").unwrap();
let result = router.execute("SIMILAR [1.0, 0.0] TOP 1").unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 1);
assert_eq!(results[0].key, "v1");
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn can_access_underlying_engines() {
let router = QueryRouter::new();
let _ = router.relational();
let _ = router.graph();
let _ = router.vector();
}
#[test]
fn with_engines_constructor() {
let rel = Arc::new(RelationalEngine::new());
let graph = Arc::new(GraphEngine::new());
let vec = Arc::new(VectorEngine::new());
let router = QueryRouter::with_engines(rel, graph, vec);
assert!(router.execute("EMBED test [1.0]").is_ok());
}
#[test]
fn build_vector_index() {
let mut router = QueryRouter::new();
router.execute("EMBED a [1.0, 0.0]").unwrap();
router.execute("EMBED b [0.0, 1.0]").unwrap();
router.build_vector_index().unwrap();
let result = router.execute("SIMILAR a TOP 2").unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 2);
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn error_display() {
let e = RouterError::ParseError("test".into());
assert!(e.to_string().contains("Parse error"));
let e = RouterError::UnknownCommand("FOO".into());
assert!(e.to_string().contains("Unknown command"));
let e = RouterError::RelationalError("db error".into());
assert!(e.to_string().contains("Relational error"));
let e = RouterError::GraphError("graph error".into());
assert!(e.to_string().contains("Graph error"));
let e = RouterError::VectorError("vec error".into());
assert!(e.to_string().contains("Vector error"));
let e = RouterError::InvalidArgument("bad arg".into());
assert!(e.to_string().contains("Invalid argument"));
let e = RouterError::MissingArgument("missing".into());
assert!(e.to_string().contains("Missing argument"));
let e = RouterError::TypeMismatch("type".into());
assert!(e.to_string().contains("Type mismatch"));
}
#[test]
fn error_clone_and_eq() {
let e1 = RouterError::ParseError("test".into());
let e2 = e1.clone();
assert_eq!(e1, e2);
}
#[test]
fn error_is_std_error() {
let error: Box<dyn std::error::Error> = Box::new(RouterError::ParseError("test".into()));
assert!(error.to_string().contains("Parse"));
}
#[test]
fn default_trait() {
let router = QueryRouter::default();
assert!(router.execute("EMBED x [1.0]").is_ok());
}
#[test]
fn parse_compound_conditions() {
let router = QueryRouter::new();
router.execute("CREATE TABLE data (a int, b int)").unwrap();
router
.execute("INSERT INTO data (a, b) VALUES (1, 2)")
.unwrap();
router
.execute("INSERT INTO data (a, b) VALUES (3, 4)")
.unwrap();
router
.execute("INSERT INTO data (a, b) VALUES (5, 6)")
.unwrap();
let result = router
.execute("SELECT * FROM data WHERE a > 2 AND b < 6")
.unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
},
_ => panic!("Expected Rows"),
}
let result = router
.execute("SELECT * FROM data WHERE a = 1 OR a = 5")
.unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 2);
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn parse_nullable_columns() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE nullable (required string, optional text)")
.unwrap();
router
.execute("INSERT INTO nullable (required, optional) VALUES ('test', NULL)")
.unwrap();
let result = router.execute("SELECT * FROM nullable").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("optional"), Some(&Value::Null));
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn update_without_where() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
router.execute("INSERT INTO t (x) VALUES (1)").unwrap();
let result = router.execute("UPDATE t x=2");
assert!(result.is_err());
}
#[test]
fn delete_without_where_clause() {
let router = QueryRouter::new();
router.execute("CREATE TABLE del (x int)").unwrap();
router.execute("INSERT INTO del (x) VALUES (1)").unwrap();
router.execute("INSERT INTO del (x) VALUES (2)").unwrap();
let result = router.execute("DELETE FROM del").unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 2),
_ => panic!("Expected Count"),
}
}
#[test]
fn create_table_with_bool() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE flags (name string, active bool)")
.unwrap();
router
.execute("INSERT INTO flags (name, active) VALUES ('test', true)")
.unwrap();
let result = router.execute("SELECT * FROM flags").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get("active"), Some(&Value::Bool(true)));
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn create_table_with_float() {
let router = QueryRouter::new();
router.execute("CREATE TABLE nums (val double)").unwrap();
router
.execute("INSERT INTO nums (val) VALUES (3.14)")
.unwrap();
let result = router.execute("SELECT * FROM nums").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn invalid_create_missing_parens() {
let router = QueryRouter::new();
let result = router.execute("CREATE TABLE bad x int");
assert!(result.is_err());
}
#[test]
fn invalid_create_command() {
let router = QueryRouter::new();
let result = router.execute("CREATE SOMETHING bad");
assert!(result.is_err());
}
#[test]
fn invalid_drop_command() {
let router = QueryRouter::new();
let result = router.execute("DROP SOMETHING bad");
assert!(result.is_err());
}
#[test]
fn path_not_found() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("PATH {} -> {}", n1, n2)).unwrap();
match result {
QueryResult::Path(path) => assert!(path.is_empty()),
_ => panic!("Expected Path"),
}
}
#[test]
fn neighbors_in_direction() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap();
let result = router
.execute(&format!("NEIGHBORS {} INCOMING", n2))
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
_ => panic!("Expected Ids"),
}
}
#[test]
fn neighbors_invalid_direction() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("NEIGHBORS {n1} INVALID"));
assert!(result.is_err());
}
#[test]
fn node_with_typed_properties() {
let router = QueryRouter::new();
let result = router
.execute("NODE CREATE person { age: 30, score: 95.5, active: true }")
.unwrap();
match result {
QueryResult::Ids(ids) => {
let node_result = router.execute(&format!("NODE GET {}", ids[0])).unwrap();
match node_result {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes[0].properties.get("age"), Some(&"30".to_string()));
},
_ => panic!("Expected Nodes"),
}
},
_ => panic!("Expected Ids"),
}
}
#[test]
fn edge_undirected() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : rel_link", n1, n2))
.unwrap();
}
#[test]
fn condition_all_operators() {
let router = QueryRouter::new();
router.execute("CREATE TABLE ops (x int)").unwrap();
router.execute("INSERT INTO ops (x) VALUES (5)").unwrap();
let result = router.execute("SELECT * FROM ops WHERE x != 10").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
_ => panic!("Expected Rows"),
}
let result = router.execute("SELECT * FROM ops WHERE x <= 5").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
_ => panic!("Expected Rows"),
}
let result = router.execute("SELECT * FROM ops WHERE x >= 5").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
_ => panic!("Expected Rows"),
}
}
#[test]
fn invalid_condition() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute("SELECT * FROM t WHERE invalid");
assert!(result.is_err());
}
#[test]
fn invalid_insert_values() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute("INSERT t invalid");
assert!(result.is_err());
}
#[test]
fn invalid_node_id() {
let router = QueryRouter::new();
let result = router.execute("NODE GET notanumber");
assert!(result.is_err());
}
#[test]
fn invalid_edge_id() {
let router = QueryRouter::new();
let result = router.execute("EDGE GET notanumber");
assert!(result.is_err());
}
#[test]
fn invalid_neighbors_id() {
let router = QueryRouter::new();
let result = router.execute("NEIGHBORS notanumber");
assert!(result.is_err());
}
#[test]
fn invalid_path_ids() {
let router = QueryRouter::new();
let result = router.execute("PATH notanumber -> 1");
assert!(result.is_err());
let result = router.execute("PATH 1 -> notanumber");
assert!(result.is_err());
}
#[test]
fn invalid_vector() {
let router = QueryRouter::new();
let result = router.execute("EMBED key [not, valid]");
assert!(result.is_err());
}
#[test]
fn empty_vector() {
let router = QueryRouter::new();
let result = router.execute("EMBED key []");
assert!(result.is_err());
}
#[test]
fn similar_with_inline_vector() {
let router = QueryRouter::new();
router.execute("EMBED v1 [1.0, 0.0, 0.0]").unwrap();
let result = router.execute("SIMILAR [0.9, 0.1, 0.0] TOP 1").unwrap();
match result {
QueryResult::Similar(results) => assert_eq!(results.len(), 1),
_ => panic!("Expected Similar"),
}
}
#[test]
fn unknown_edge_subcommand() {
let router = QueryRouter::new();
let result = router.execute("EDGE UNKNOWN 1");
assert!(result.is_err());
}
#[test]
fn unknown_node_subcommand() {
let router = QueryRouter::new();
let result = router.execute("NODE UNKNOWN label");
assert!(result.is_err());
}
#[test]
fn find_with_where_clause() {
let router = QueryRouter::new();
router.execute("NODE CREATE item { x: 10 }").unwrap();
router.execute("NODE CREATE item { x: 3 }").unwrap();
router.execute("NODE CREATE item { x: 7 }").unwrap();
let result = router.execute("FIND NODES item WHERE x > 5").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.items.len() <= 3);
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn property_value_null() {
let router = QueryRouter::new();
router.execute("NODE CREATE test { val: NULL }").unwrap();
}
#[test]
fn property_value_false() {
let router = QueryRouter::new();
router.execute("NODE CREATE test { val: false }").unwrap();
}
#[test]
fn missing_edge_definition() {
let router = QueryRouter::new();
let result = router.execute("EDGE CREATE");
assert!(result.is_err());
}
#[test]
fn missing_path_args() {
let router = QueryRouter::new();
let result = router.execute("PATH 1");
assert!(result.is_err());
}
#[test]
fn missing_embed_args() {
let router = QueryRouter::new();
let result = router.execute("EMBED");
assert!(result.is_err());
}
#[test]
fn missing_similar_args() {
let router = QueryRouter::new();
let result = router.execute("SIMILAR");
assert!(result.is_err());
}
#[test]
fn find_without_args_returns_all_nodes() {
let router = QueryRouter::new();
router.execute("NODE CREATE test { name: 'A' }").unwrap();
router.execute("NODE CREATE test { name: 'B' }").unwrap();
let result = router.execute("FIND").unwrap();
match result {
QueryResult::Unified(u) => {
assert_eq!(u.items.len(), 2);
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn create_index_missing_args() {
let router = QueryRouter::new();
let result = router.execute("CREATE INDEX t");
assert!(result.is_err());
}
#[test]
fn drop_index_missing_args() {
let router = QueryRouter::new();
let result = router.execute("DROP INDEX t");
assert!(result.is_err());
}
#[test]
fn invalid_top_value() {
let router = QueryRouter::new();
router.execute("EMBED v [1.0]").unwrap();
let result = router.execute("SIMILAR v TOP notanumber");
assert!(result.is_err());
}
#[test]
fn hnsw_similar_search() {
let mut router = QueryRouter::new();
router.execute("EMBED a [1.0, 0.0]").unwrap();
router.execute("EMBED b [0.0, 1.0]").unwrap();
router.build_vector_index().unwrap();
let result = router.execute("SIMILAR a TOP 2").unwrap();
match result {
QueryResult::Similar(results) => assert_eq!(results.len(), 2),
_ => panic!("Expected Similar"),
}
}
#[test]
fn invalid_edge_nodes() {
let router = QueryRouter::new();
let result = router.execute("EDGE CREATE notanumber -> 1");
assert!(result.is_err());
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("EDGE CREATE {} -> notanumber", n1));
assert!(result.is_err());
}
#[test]
fn missing_insert_table() {
let router = QueryRouter::new();
let result = router.execute("INSERT");
assert!(result.is_err());
}
#[test]
fn missing_delete_table() {
let router = QueryRouter::new();
let result = router.execute("DELETE");
assert!(result.is_err());
}
#[test]
fn update_with_set_no_where() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
router.execute("INSERT INTO t (x) VALUES (1)").unwrap();
router.execute("INSERT INTO t (x) VALUES (2)").unwrap();
let result = router.execute("UPDATE t SET x=99").unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 2),
_ => panic!("Expected Count"),
}
}
#[test]
fn invalid_column_definition() {
let router = QueryRouter::new();
let result = router.execute("CREATE TABLE bad (invalid)");
assert!(result.is_err());
}
#[test]
fn unknown_column_type() {
let router = QueryRouter::new();
let result = router.execute("CREATE TABLE bad (x unknowntype)");
assert!(result.is_err());
}
#[test]
fn node_get_missing_id() {
let router = QueryRouter::new();
let result = router.execute("NODE GET");
assert!(result.is_err());
}
#[test]
fn node_delete_missing_id() {
let router = QueryRouter::new();
let result = router.execute("NODE DELETE");
assert!(result.is_err());
}
#[test]
fn edge_missing_subcommand() {
let router = QueryRouter::new();
let result = router.execute("EDGE");
assert!(result.is_err());
}
#[test]
fn edge_get_missing_id() {
let router = QueryRouter::new();
let result = router.execute("EDGE GET");
assert!(result.is_err());
}
#[test]
fn neighbors_default_direction() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("NEIGHBORS {}", n1)).unwrap();
match result {
QueryResult::Ids(_) => {},
_ => panic!("Expected Ids"),
}
}
#[test]
fn invalid_edge_definition_format() {
let router = QueryRouter::new();
let result = router.execute("EDGE CREATE 1 2 label");
assert!(result.is_err());
}
#[test]
fn edge_directed_keyword() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : rel_link", n1, n2))
.unwrap();
}
#[test]
fn value_parsing_false_lowercase() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (flag bool)").unwrap();
router
.execute("INSERT INTO t (flag) VALUES (FALSE)")
.unwrap();
let result = router.execute("SELECT * FROM t").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows[0].get("flag"), Some(&Value::Bool(false)));
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn value_parsing_string_variants() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (s string)").unwrap();
router
.execute("INSERT INTO t (s) VALUES ('hello')")
.unwrap();
let result = router.execute("SELECT * FROM t").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows[0].get("s"), Some(&Value::String("hello".into())));
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn property_null_to_string() {
let router = QueryRouter::new();
let result = router.execute("NODE CREATE test { prop: NULL }").unwrap();
match result {
QueryResult::Ids(ids) => {
let node = router.execute(&format!("NODE GET {}", ids[0])).unwrap();
match node {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes[0].properties.get("prop"), Some(&"null".to_string()));
},
_ => panic!("Expected Nodes"),
}
},
_ => panic!("Expected Ids"),
}
}
#[test]
fn missing_neighbors_id() {
let router = QueryRouter::new();
let result = router.execute("NEIGHBORS");
assert!(result.is_err());
}
#[test]
fn select_missing_table() {
let router = QueryRouter::new();
let result = router.execute("SELECT");
assert!(result.is_err());
}
#[test]
fn node_missing_subcommand() {
let router = QueryRouter::new();
let result = router.execute("NODE");
assert!(result.is_err());
}
#[test]
fn unquoted_string_value() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (s string)").unwrap();
router
.execute("INSERT INTO t (s) VALUES (bareword)")
.unwrap();
let result = router.execute("SELECT * FROM t").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows[0].get("s"), Some(&Value::String("bareword".into())));
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn whitespace_only_command() {
let router = QueryRouter::new();
let result = router.execute(" ");
assert!(result.is_err());
}
#[test]
fn path_graph_error() {
let router = QueryRouter::new();
let result = router.execute("PATH 99999 -> 99998");
assert!(result.is_err());
}
#[test]
fn node_property_unquoted_string() {
let router = QueryRouter::new();
let result = router
.execute("NODE CREATE test { prop: somevalue }")
.unwrap();
match result {
QueryResult::Ids(ids) => {
let node = router.execute(&format!("NODE GET {}", ids[0])).unwrap();
match node {
QueryResult::Nodes(nodes) => {
assert_eq!(
nodes[0].properties.get("prop"),
Some(&"somevalue".to_string())
);
},
_ => panic!("Expected Nodes"),
}
},
_ => panic!("Expected Ids"),
}
}
#[test]
fn edge_missing_arrow_definition() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("EDGE CREATE {} {} label", n1, n1 + 1));
assert!(result.is_err());
}
#[test]
fn embed_with_empty_brackets() {
let router = QueryRouter::new();
let result = router.execute("EMBED emptykey []");
assert!(result.is_err());
}
#[test]
fn show_vector_index_empty() {
let router = QueryRouter::new();
let result = router.execute("SHOW VECTOR INDEX").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("No HNSW index built"));
},
_ => panic!("Expected Value result"),
}
}
#[test]
fn show_vector_index_after_build() {
let mut router = QueryRouter::new();
router.execute("EMBED v1 [1.0, 0.0, 0.0]").unwrap();
router.execute("EMBED v2 [0.0, 1.0, 0.0]").unwrap();
router.execute("EMBED v3 [0.0, 0.0, 1.0]").unwrap();
router.build_vector_index().unwrap();
let result = router.execute("SHOW VECTOR INDEX").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("HNSW index"));
assert!(s.contains("3"));
},
_ => panic!("Expected Value result"),
}
}
#[test]
fn insert_table_only() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute("INSERT t");
assert!(result.is_err());
}
#[test]
fn edge_definition_too_short() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("EDGE CREATE {} ->", n1));
assert!(result.is_err());
}
#[test]
fn find_edges_returns_items() {
let router = QueryRouter::new();
let user_id = match router
.execute("NODE CREATE user { name: 'Alice' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let post_id = match router
.execute("NODE CREATE post { title: 'Hello' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!(
"EDGE CREATE {} -> {} : authored",
user_id, post_id
))
.unwrap();
let result = router.execute("FIND EDGES authored").unwrap();
match result {
QueryResult::Unified(unified) => {
assert!(!unified.items.is_empty());
assert_eq!(unified.items[0].source, "graph");
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn similar_no_results() {
let router = QueryRouter::new();
let result = router.execute("SIMILAR nonexistent TOP 5");
assert!(result.is_err());
}
#[test]
fn parsed_select_basic() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE users (id int, name string)")
.unwrap();
router
.execute("INSERT INTO users (id, name) VALUES (1, 'alice')")
.unwrap();
let result = router.execute_parsed("SELECT * FROM users").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
_ => panic!("Expected Rows"),
}
}
#[test]
fn parsed_select_with_where() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE products (id int, price int)")
.unwrap();
router
.execute("INSERT INTO products (id, price) VALUES (1, 100)")
.unwrap();
router
.execute("INSERT INTO products (id, price) VALUES (2, 200)")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM products WHERE price > 150")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
_ => panic!("Expected Rows"),
}
}
#[test]
fn parsed_insert_values() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE items (id int, name string)")
.unwrap();
let result = router
.execute_parsed("INSERT INTO items (id, name) VALUES (1, 'test')")
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
_ => panic!("Expected Ids"),
}
}
#[test]
fn parsed_update() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE scores (id int, val int)")
.unwrap();
router
.execute("INSERT INTO scores (id, val) VALUES (1, 10)")
.unwrap();
let result = router
.execute_parsed("UPDATE scores SET val = 20 WHERE id = 1")
.unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 1),
_ => panic!("Expected Count"),
}
}
#[test]
fn parsed_update_no_where() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
router.execute("INSERT INTO t (x) VALUES (1)").unwrap();
router.execute("INSERT INTO t (x) VALUES (2)").unwrap();
let result = router.execute_parsed("UPDATE t SET x = 99").unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 2),
_ => panic!("Expected Count"),
}
}
#[test]
fn parsed_delete() {
let router = QueryRouter::new();
router.execute("CREATE TABLE temps (id int)").unwrap();
router.execute("INSERT INTO temps (id) VALUES (1)").unwrap();
router.execute("INSERT INTO temps (id) VALUES (2)").unwrap();
let result = router
.execute_parsed("DELETE FROM temps WHERE id = 1")
.unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 1),
_ => panic!("Expected Count"),
}
}
#[test]
fn parsed_delete_no_where() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
router.execute("INSERT INTO t (x) VALUES (1)").unwrap();
router.execute("INSERT INTO t (x) VALUES (2)").unwrap();
let result = router.execute_parsed("DELETE FROM t").unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 2),
_ => panic!("Expected Count"),
}
}
#[test]
fn parsed_create_table() {
let router = QueryRouter::new();
let result = router
.execute_parsed("CREATE TABLE newtbl (id INTEGER, name VARCHAR(100))")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
router
.execute("INSERT INTO newtbl (id, name) VALUES (1, 'test')")
.unwrap();
}
#[test]
fn parsed_create_table_not_null() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE required (id INT NOT NULL, name TEXT)")
.unwrap();
}
#[test]
fn parsed_drop_table() {
let router = QueryRouter::new();
router.execute("CREATE TABLE todrop (id int)").unwrap();
let result = router.execute_parsed("DROP TABLE todrop").unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn parsed_create_index() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE indexed (id int, val int)")
.unwrap();
let result = router
.execute_parsed("CREATE INDEX idx ON indexed (val)")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn parsed_drop_index_not_supported() {
let router = QueryRouter::new();
let result = router.execute_parsed("DROP INDEX myindex");
assert!(result.is_err());
}
#[test]
fn parsed_node_create() {
let router = QueryRouter::new();
let result = router
.execute_parsed("NODE CREATE person { name: 'Alice', age: 30 }")
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
_ => panic!("Expected Ids"),
}
}
#[test]
fn parsed_node_get() {
let router = QueryRouter::new();
let id = match router.execute("NODE CREATE test").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute_parsed(&format!("NODE GET {}", id)).unwrap();
match result {
QueryResult::Nodes(nodes) => assert_eq!(nodes.len(), 1),
_ => panic!("Expected Nodes"),
}
}
#[test]
fn parsed_node_delete() {
let router = QueryRouter::new();
let id = match router.execute("NODE CREATE todelete").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute_parsed(&format!("NODE DELETE {}", id))
.unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 1),
_ => panic!("Expected Count"),
}
}
#[test]
fn parsed_node_list() {
let router = QueryRouter::new();
router.execute("NODE CREATE label1").unwrap();
let result = router.execute_parsed("NODE LIST").unwrap();
assert!(matches!(result, QueryResult::Nodes(_)));
}
#[test]
fn parsed_edge_create() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute_parsed(&format!(
"EDGE CREATE {} -> {} : knows {{ since: 2020 }}",
n1, n2
))
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
_ => panic!("Expected Ids"),
}
}
#[test]
fn parsed_edge_get() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE x").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE y").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let edge_id = match router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute_parsed(&format!("EDGE GET {}", edge_id))
.unwrap();
match result {
QueryResult::Edges(edges) => assert_eq!(edges.len(), 1),
_ => panic!("Expected Edges"),
}
}
#[test]
fn parsed_edge_list() {
let router = QueryRouter::new();
let result = router.execute_parsed("EDGE LIST").unwrap();
assert!(matches!(result, QueryResult::Edges(_)));
}
#[test]
fn parsed_edge_delete_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("EDGE DELETE 999999");
assert!(result.is_err());
}
#[test]
fn parsed_edge_delete_success() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE TestNode").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE TestNode").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let edge_id = match router
.execute(&format!(
"EDGE CREATE {} -> {} : test_edge {{ weight: 0.5 }}",
a, b
))
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute_parsed(&format!("EDGE DELETE {}", edge_id));
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Count(c) => assert_eq!(c, 1),
_ => panic!("Expected Count result"),
}
}
#[test]
fn parsed_neighbors() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE start").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE neighbor").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap();
let result = router
.execute_parsed(&format!("NEIGHBORS {} OUTGOING", n1))
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
_ => panic!("Expected Ids"),
}
}
#[test]
fn parsed_neighbors_incoming() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap();
let result = router
.execute_parsed(&format!("NEIGHBORS {} INCOMING", n2))
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
_ => panic!("Expected Ids"),
}
}
#[test]
fn parsed_neighbors_both() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap();
let result = router
.execute_parsed(&format!("NEIGHBORS {} BOTH", n1))
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn parsed_path() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE source").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE target").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap();
let result = router
.execute_parsed(&format!("PATH SHORTEST {} -> {}", n1, n2))
.unwrap();
match result {
QueryResult::Path(path) => assert!(!path.is_empty()),
_ => panic!("Expected Path"),
}
}
#[test]
fn parsed_embed_store() {
let router = QueryRouter::new();
let result = router
.execute_parsed("EMBED STORE 'key1' [1.0, 2.0, 3.0]")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn parsed_embed_get() {
let router = QueryRouter::new();
router.execute("EMBED vec1 [1.0, 2.0, 3.0]").unwrap();
let result = router.execute_parsed("EMBED GET 'vec1'").unwrap();
match result {
QueryResult::Value(s) => assert!(s.contains("1")),
_ => panic!("Expected Value"),
}
}
#[test]
fn parsed_embed_delete() {
let router = QueryRouter::new();
router.execute("EMBED todelete [1.0, 2.0]").unwrap();
let result = router.execute_parsed("EMBED DELETE 'todelete'").unwrap();
match result {
QueryResult::Count(n) => assert_eq!(n, 1),
_ => panic!("Expected Count"),
}
}
#[test]
fn parsed_similar_by_key() {
let router = QueryRouter::new();
router.execute("EMBED item1 [1.0, 0.0, 0.0]").unwrap();
router.execute("EMBED item2 [0.9, 0.1, 0.0]").unwrap();
let result = router.execute_parsed("SIMILAR 'item1' LIMIT 5").unwrap();
match result {
QueryResult::Similar(results) => assert!(!results.is_empty()),
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_by_vector() {
let router = QueryRouter::new();
router.execute("EMBED vec1 [1.0, 0.0, 0.0]").unwrap();
router.execute("EMBED vec2 [0.0, 1.0, 0.0]").unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0, 0.0] LIMIT 5")
.unwrap();
match result {
QueryResult::Similar(results) => assert!(!results.is_empty()),
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_with_hnsw() {
let mut router = QueryRouter::new();
router.execute("EMBED a [1.0, 0.0]").unwrap();
router.execute("EMBED b [0.0, 1.0]").unwrap();
router.build_vector_index().unwrap();
let result = router.execute_parsed("SIMILAR 'a' LIMIT 2").unwrap();
match result {
QueryResult::Similar(results) => assert_eq!(results.len(), 2),
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_cosine_metric() {
let router = QueryRouter::new();
router.execute("EMBED cos_a [1.0, 0.0]").unwrap();
router.execute("EMBED cos_b [0.0, 1.0]").unwrap();
router.execute("EMBED cos_c [0.707, 0.707]").unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0] COSINE LIMIT 3")
.unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 3);
assert_eq!(results[0].key, "cos_a");
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_euclidean_metric() {
let router = QueryRouter::new();
router.execute("EMBED euc_a [1.0, 0.0]").unwrap();
router.execute("EMBED euc_b [2.0, 0.0]").unwrap();
router.execute("EMBED euc_c [10.0, 0.0]").unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0] EUCLIDEAN LIMIT 3")
.unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 3);
assert_eq!(results[0].key, "euc_a");
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_euclidean_zero_query() {
let router = QueryRouter::new();
router.execute("EMBED zero_origin [0.0, 0.0]").unwrap();
router.execute("EMBED zero_unit [1.0, 0.0]").unwrap();
router.execute("EMBED zero_far [10.0, 0.0]").unwrap();
let result = router
.execute_parsed("SIMILAR [0.0, 0.0] EUCLIDEAN LIMIT 3")
.unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(
results.len(),
3,
"Should return 3 results for EUCLIDEAN with zero query"
);
assert_eq!(results[0].key, "zero_origin");
assert!((results[0].score - 1.0).abs() < 0.01);
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_dot_product_metric() {
let router = QueryRouter::new();
router.execute("EMBED dot_a [1.0, 0.0]").unwrap();
router.execute("EMBED dot_b [2.0, 0.0]").unwrap();
router.execute("EMBED dot_c [0.5, 0.0]").unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0] DOT_PRODUCT LIMIT 3")
.unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 3);
assert_eq!(results[0].key, "dot_b");
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_hnsw_falls_back_for_non_cosine() {
let mut router = QueryRouter::new();
router.execute("EMBED hnsw_a [1.0, 0.0]").unwrap();
router.execute("EMBED hnsw_b [2.0, 0.0]").unwrap();
router.build_vector_index().unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0] EUCLIDEAN LIMIT 2")
.unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 2);
assert_eq!(results[0].key, "hnsw_a");
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_find_nodes() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND NODE person").unwrap();
match result {
QueryResult::Unified(unified) => {
assert!(unified.description.contains("node"));
assert!(unified.description.contains("'person'"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND EDGE knows").unwrap();
match result {
QueryResult::Unified(unified) => {
assert!(unified.description.contains("edge"));
assert!(unified.description.contains("'knows'"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_with_where() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'Alice', age: 25 }")
.unwrap();
let result = router.execute_parsed("FIND NODE WHERE age > 18").unwrap();
match result {
QueryResult::Unified(unified) => {
assert!(unified.description.contains("node"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_where_eq() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE user { name: 'Bob', status: 'active' }")
.unwrap();
router
.execute_parsed("NODE CREATE user { name: 'Eve', status: 'inactive' }")
.unwrap();
let result = router
.execute_parsed("FIND NODE WHERE status = 'active'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Bob".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_where_gt() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'Young', age: 15 }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'Adult', age: 30 }")
.unwrap();
let result = router.execute_parsed("FIND NODE WHERE age > 20").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(!u.items.is_empty());
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Adult".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_where_and() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE user { name: 'Alice', age: 25, role: 'admin' }")
.unwrap();
router
.execute_parsed("NODE CREATE user { name: 'Bob', age: 35, role: 'user' }")
.unwrap();
let result = router
.execute_parsed("FIND NODE WHERE age > 20 AND role = 'admin'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Alice".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_where_lt() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'Young', age: 15 }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'Adult', age: 30 }")
.unwrap();
let result = router.execute_parsed("FIND NODE WHERE age < 20").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Young".to_string())));
assert!(!u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Adult".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_where_le() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'Young', age: 20 }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'Adult', age: 30 }")
.unwrap();
let result = router.execute_parsed("FIND NODE WHERE age <= 20").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Young".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_where_ge() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'Young', age: 15 }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'Adult', age: 30 }")
.unwrap();
let result = router.execute_parsed("FIND NODE WHERE age >= 30").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Adult".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_where_or() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE user { name: 'Alice', role: 'admin' }")
.unwrap();
router
.execute_parsed("NODE CREATE user { name: 'Bob', role: 'guest' }")
.unwrap();
router
.execute_parsed("NODE CREATE user { name: 'Eve', role: 'user' }")
.unwrap();
let result = router
.execute_parsed("FIND NODE WHERE role = 'admin' OR role = 'guest'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Alice".to_string())));
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"Bob".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_id_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE user { name: 'First' }")
.unwrap();
router
.execute_parsed("NODE CREATE user { name: 'Second' }")
.unwrap();
let result = router.execute_parsed("FIND NODE WHERE id = 1").unwrap();
match result {
QueryResult::Unified(u) => {
assert_eq!(u.items.len(), 1);
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"First".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_condition_no_match() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE user { name: 'Test', age: 25 }")
.unwrap();
let result = router
.execute_parsed("FIND NODE WHERE nonexistent = 'value'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.items.is_empty());
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn vault_accessor() {
let router = QueryRouter::new();
assert!(router.vault().is_none());
}
#[test]
fn error_from_cache_error() {
let cache_err = tensor_cache::CacheError::NotFound("test".to_string());
let router_err: RouterError = cache_err.into();
assert!(matches!(router_err, RouterError::CacheError(_)));
}
#[test]
fn parsed_find_edge_by_type() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE x { name: 'X' }")
.unwrap();
router
.execute_parsed("NODE CREATE y { name: 'Y' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : special_type")
.unwrap();
let result = router
.execute_parsed("FIND EDGE WHERE edge_type = 'special_type'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn blobs_similar_to_key() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.execute_parsed("EMBED STORE 'blob_a' [1.0, 0.0, 0.0, 0.0]")
.unwrap();
router
.execute_parsed("EMBED STORE 'blob_b' [0.9, 0.1, 0.0, 0.0]")
.unwrap();
router
.execute_parsed("EMBED STORE 'blob_c' [0.0, 1.0, 0.0, 0.0]")
.unwrap();
let result = router.execute_parsed("BLOBS SIMILAR TO 'blob_a' LIMIT 2");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn blob_put_with_full_options() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed(
"BLOB PUT 'test_file.json' DATA '{\"key\":\"value\"}' TYPE 'application/json' BY 'testuser'",
);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_find_edges_with_edge_props() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'X' }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'Y' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : works_at { department: 'engineering', level: 3 }")
.unwrap();
let result = router.execute_parsed("FIND EDGE works_at").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
assert!(!u.items.is_empty());
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_scan_with_properties() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE item { name: 'Item1', price: 100, active: true }")
.unwrap();
router
.execute_parsed("NODE CREATE item { name: 'Item2', price: 200, active: false }")
.unwrap();
let result = router.execute_parsed("FIND NODE item").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.items.len() >= 2);
assert!(u.items.iter().any(|item| item.data.contains_key("name")));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_where() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'A' }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'B' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : friend { strength: 10 }")
.unwrap();
let result = router
.execute_parsed("FIND EDGE WHERE strength > 5")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_type_eq() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE x { name: 'X' }")
.unwrap();
router
.execute_parsed("NODE CREATE y { name: 'Y' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : knows { since: 2020 }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : works { since: 2021 }")
.unwrap();
let result = router.execute_parsed("FIND EDGE knows").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(!u.items.is_empty());
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_and_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE a { name: 'A' }")
.unwrap();
router
.execute_parsed("NODE CREATE b { name: 'B' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { weight: 50, active: true }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { weight: 10, active: false }")
.unwrap();
let result = router
.execute_parsed("FIND EDGE WHERE weight > 20 AND active = true")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_or_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE a { name: 'A' }")
.unwrap();
router
.execute_parsed("NODE CREATE b { name: 'B' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { status: 'active' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { status: 'pending' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { status: 'archived' }")
.unwrap();
let result = router
.execute_parsed("FIND EDGE WHERE status = 'active' OR status = 'pending'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_with_ne_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE user { name: 'Admin', role: 'admin' }")
.unwrap();
router
.execute_parsed("NODE CREATE user { name: 'User', role: 'user' }")
.unwrap();
let result = router
.execute_parsed("FIND NODE WHERE role != 'admin'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("node"));
assert!(u
.items
.iter()
.any(|item| item.data.get("name") == Some(&"User".to_string())));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_ne_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE n { name: 'N1' }")
.unwrap();
router
.execute_parsed("NODE CREATE n { name: 'N2' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { status: 'complete' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { status: 'pending' }")
.unwrap();
let result = router
.execute_parsed("FIND EDGE WHERE status != 'complete'")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_lt_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE n { name: 'N1' }")
.unwrap();
router
.execute_parsed("NODE CREATE n { name: 'N2' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { weight: 100 }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { weight: 10 }")
.unwrap();
let result = router
.execute_parsed("FIND EDGE WHERE weight < 50")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_ge_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE n { name: 'N1' }")
.unwrap();
router
.execute_parsed("NODE CREATE n { name: 'N2' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { priority: 5 }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { priority: 10 }")
.unwrap();
let result = router
.execute_parsed("FIND EDGE WHERE priority >= 5")
.unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_edges_with_le_condition() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE n { name: 'N1' }")
.unwrap();
router
.execute_parsed("NODE CREATE n { name: 'N2' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { score: 3 }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : rel { score: 8 }")
.unwrap();
let result = router.execute_parsed("FIND EDGE WHERE score <= 5").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.description.contains("edge"));
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_with_limit_verified() {
let router = QueryRouter::new();
for i in 0..10 {
router
.execute_parsed(&format!("NODE CREATE item {{ idx: {} }}", i))
.unwrap();
}
let result = router.execute_parsed("FIND NODE item LIMIT 3").unwrap();
match result {
QueryResult::Unified(u) => {
assert!(u.items.len() <= 3);
},
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_node_list_with_data() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE employee { name: 'John', dept: 'sales' }")
.unwrap();
router
.execute_parsed("NODE CREATE employee { name: 'Jane', dept: 'eng' }")
.unwrap();
router
.execute_parsed("NODE CREATE manager { name: 'Boss', level: 5 }")
.unwrap();
let result = router.execute_parsed("NODE LIST employee").unwrap();
match result {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes.len(), 2); },
_ => panic!("Expected Nodes"),
}
let all = router.execute_parsed("NODE LIST").unwrap();
match all {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes.len(), 3); },
_ => panic!("Expected Nodes"),
}
}
#[test]
fn parsed_edge_list_with_data() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person { name: 'X' }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'Y' }")
.unwrap();
router
.execute_parsed("NODE CREATE person { name: 'Z' }")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 2 : friend")
.unwrap();
router
.execute_parsed("EDGE CREATE 2 -> 3 : colleague")
.unwrap();
router
.execute_parsed("EDGE CREATE 1 -> 3 : friend")
.unwrap();
let result = router.execute_parsed("EDGE LIST friend").unwrap();
match result {
QueryResult::Edges(edges) => {
assert_eq!(edges.len(), 2); },
_ => panic!("Expected Edges"),
}
let all = router.execute_parsed("EDGE LIST").unwrap();
match all {
QueryResult::Edges(edges) => {
assert_eq!(edges.len(), 3); },
_ => panic!("Expected Edges"),
}
}
#[test]
fn parsed_empty_statement() {
let router = QueryRouter::new();
let result = router.execute_parsed(";").unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn parsed_parse_error() {
let router = QueryRouter::new();
let result = router.execute_parsed("INVALID SYNTAX HERE @#$");
assert!(result.is_err());
if let Err(RouterError::ParseError(msg)) = result {
assert!(!msg.is_empty());
} else {
panic!("Expected ParseError");
}
}
#[test]
fn parsed_select_missing_from() {
let router = QueryRouter::new();
let result = router.execute_parsed("SELECT *");
assert!(result.is_err());
}
#[test]
fn parsed_insert_select_basic() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE src (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("CREATE TABLE dst (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO src VALUES (1, 'Alice')")
.unwrap();
router
.execute_parsed("INSERT INTO src VALUES (2, 'Bob')")
.unwrap();
let result = router.execute_parsed("INSERT INTO dst SELECT * FROM src");
assert!(result.is_ok());
let rows = router.execute_parsed("SELECT * FROM dst").unwrap();
match rows {
QueryResult::Rows(r) => {
assert_eq!(r.len(), 2);
},
_ => panic!("expected Rows"),
}
}
#[test]
fn parsed_condition_operators() {
let router = QueryRouter::new();
router.execute("CREATE TABLE vals (id int, x int)").unwrap();
router
.execute("INSERT INTO vals (id, x) VALUES (1, 10)")
.unwrap();
router
.execute("INSERT INTO vals (id, x) VALUES (2, 20)")
.unwrap();
router
.execute("INSERT INTO vals (id, x) VALUES (3, 30)")
.unwrap();
let eq = router
.execute_parsed("SELECT * FROM vals WHERE x = 20")
.unwrap();
assert!(matches!(eq, QueryResult::Rows(r) if r.len() == 1));
let ne = router
.execute_parsed("SELECT * FROM vals WHERE x != 20")
.unwrap();
assert!(matches!(ne, QueryResult::Rows(r) if r.len() == 2));
let lt = router
.execute_parsed("SELECT * FROM vals WHERE x < 20")
.unwrap();
assert!(matches!(lt, QueryResult::Rows(r) if r.len() == 1));
let le = router
.execute_parsed("SELECT * FROM vals WHERE x <= 20")
.unwrap();
assert!(matches!(le, QueryResult::Rows(r) if r.len() == 2));
let gt = router
.execute_parsed("SELECT * FROM vals WHERE x > 20")
.unwrap();
assert!(matches!(gt, QueryResult::Rows(r) if r.len() == 1));
let ge = router
.execute_parsed("SELECT * FROM vals WHERE x >= 20")
.unwrap();
assert!(matches!(ge, QueryResult::Rows(r) if r.len() == 2));
}
#[test]
fn parsed_condition_and_or() {
let router = QueryRouter::new();
router.execute("CREATE TABLE multi (a int, b int)").unwrap();
router
.execute("INSERT INTO multi (a, b) VALUES (1, 1)")
.unwrap();
router
.execute("INSERT INTO multi (a, b) VALUES (1, 2)")
.unwrap();
router
.execute("INSERT INTO multi (a, b) VALUES (2, 1)")
.unwrap();
let and_result = router
.execute_parsed("SELECT * FROM multi WHERE a = 1 AND b = 1")
.unwrap();
assert!(matches!(and_result, QueryResult::Rows(r) if r.len() == 1));
let or_result = router
.execute_parsed("SELECT * FROM multi WHERE a = 2 OR b = 2")
.unwrap();
assert!(matches!(or_result, QueryResult::Rows(r) if r.len() == 2));
}
#[test]
fn parsed_data_types() {
let router = QueryRouter::new();
router
.execute_parsed(
"CREATE TABLE types (
i INT,
bi BIGINT,
si SMALLINT,
f FLOAT,
d DOUBLE,
r REAL,
dec DECIMAL(10, 2),
num NUMERIC(5),
vc VARCHAR(255),
c CHAR(10),
t TEXT,
b BOOLEAN
)",
)
.unwrap();
}
#[test]
fn parsed_expr_to_value_types() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE vals (n int, f double, s string, b bool)")
.unwrap();
router
.execute_parsed("INSERT INTO vals (n, f, s, b) VALUES (42, 3.14, 'hello', true)")
.unwrap();
router
.execute_parsed("INSERT INTO vals (n, f, s, b) VALUES (0, 0.0, 'world', false)")
.unwrap();
let result = router.execute("SELECT * FROM vals").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
_ => panic!("Expected Rows"),
}
}
#[test]
fn parsed_neighbors_with_edge_type() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : knows", n1, n2))
.unwrap();
let result = router
.execute_parsed(&format!("NEIGHBORS {} OUTGOING : knows", n1))
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn parsed_find_with_limit() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND NODE person LIMIT 5").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
}
#[test]
fn parsed_insert_null_value() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE ntest (id INT NOT NULL, val TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO ntest (id, val) VALUES (1, NULL)")
.unwrap();
}
#[test]
fn parsed_node_create_with_properties() {
let router = QueryRouter::new();
let result = router
.execute_parsed(
"NODE CREATE person { name: 'John', age: 30, score: 95.5, active: true }",
)
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn parsed_path_not_found() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute_parsed(&format!("PATH SHORTEST {} -> {}", n1, n2))
.unwrap();
match result {
QueryResult::Path(path) => assert!(path.is_empty()),
_ => panic!("Expected Path"),
}
}
#[test]
fn parsed_select_qualified_column() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
router.execute("INSERT INTO t (x) VALUES (1)").unwrap();
let result = router.execute_parsed("SELECT t.x FROM t").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn parsed_insert_with_ident_value() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (name string)").unwrap();
let result = router.execute_parsed("INSERT INTO t (name) VALUES (someident)");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_similar_with_limit_expr() {
let router = QueryRouter::new();
router.execute("EMBED v1 [1.0, 0.0]").unwrap();
router.execute("EMBED v2 [0.0, 1.0]").unwrap();
let result = router.execute_parsed("SIMILAR 'v1' LIMIT 10").unwrap();
assert!(matches!(result, QueryResult::Similar(_)));
}
#[test]
fn parsed_embed_store_with_list() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'stored_vec' [1.0, 2.0, 3.0]")
.unwrap();
let result = router.execute_parsed("EMBED GET 'stored_vec'").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn parsed_empty_command() {
let router = QueryRouter::new();
let result = router.execute_parsed("");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_whitespace_only() {
let router = QueryRouter::new();
let result = router.execute_parsed(" ");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_create_index_empty_columns() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute_parsed("CREATE INDEX idx ON t (x)");
assert!(result.is_ok());
}
#[test]
fn parsed_find_path_pattern() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND a -[e]-> b");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_edge_create_with_type_and_props() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute_parsed(&format!(
"EDGE CREATE {} -> {} : friend {{ since: 2020 }}",
n1, n2
))
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn parsed_node_create_null_property() {
let router = QueryRouter::new();
let result = router
.execute_parsed("NODE CREATE test { val: NULL }")
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn parsed_node_create_bool_property() {
let router = QueryRouter::new();
let result = router
.execute_parsed("NODE CREATE test { active: false }")
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn parsed_node_create_float_property() {
let router = QueryRouter::new();
let result = router
.execute_parsed("NODE CREATE test { score: 3.14 }")
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn parsed_embed_with_int_values() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'intvec' [1, 2, 3]")
.unwrap();
}
#[test]
fn parsed_node_with_ident_property() {
let router = QueryRouter::new();
let result = router
.execute_parsed("NODE CREATE test { mykey: somevalue }")
.unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn execute_empty_after_trim() {
let router = QueryRouter::new();
let result = router.execute(" \t\n ");
assert!(result.is_err());
}
#[test]
fn parsed_select_with_qualified_where() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
router.execute("INSERT INTO t (x) VALUES (5)").unwrap();
let result = router
.execute_parsed("SELECT * FROM t WHERE t.x = 5")
.unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn parsed_unsupported_operator_in_where() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute_parsed("SELECT * FROM t WHERE x + 1");
assert!(result.is_err());
}
#[test]
fn parsed_literal_in_where() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute_parsed("SELECT * FROM t WHERE 1");
assert!(result.is_err());
}
#[test]
fn parsed_insert_with_complex_expr() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute_parsed("INSERT INTO t (x) VALUES (1 + 2)");
assert!(result.is_err());
}
#[test]
fn parsed_create_unsupported_type() {
let router = QueryRouter::new();
let result = router.execute_parsed("CREATE TABLE t (data jsonb)");
assert!(result.is_err());
}
#[test]
fn parsed_similar_limit_not_integer() {
let router = QueryRouter::new();
router.execute("EMBED v [1.0, 2.0]").unwrap();
let result = router.execute_parsed("SIMILAR 'v' LIMIT 'ten'");
assert!(result.is_err());
}
#[test]
fn parsed_neighbors_negative_id() {
let router = QueryRouter::new();
let result = router.execute_parsed("NEIGHBORS -1 OUTGOING");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_path_negative_ids() {
let router = QueryRouter::new();
let result = router.execute_parsed("PATH SHORTEST -1 -> -2");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_find_edges_plain() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND EDGE").unwrap();
match result {
QueryResult::Unified(u) => assert!(u.description.contains("edge")),
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_find_nodes_plain() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND NODE").unwrap();
match result {
QueryResult::Unified(u) => assert!(u.description.contains("node")),
_ => panic!("Expected Unified"),
}
}
#[test]
fn parsed_embed_get_with_ident_key() {
let router = QueryRouter::new();
router.execute("EMBED mykey [1.0, 2.0]").unwrap();
let result = router.execute_parsed("EMBED GET mykey").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn parsed_similar_with_ident_key() {
let router = QueryRouter::new();
router.execute("EMBED vec1 [1.0, 0.0]").unwrap();
let result = router.execute_parsed("SIMILAR vec1 LIMIT 5").unwrap();
assert!(matches!(result, QueryResult::Similar(_)));
}
#[test]
fn parsed_node_get_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("NODE GET 999999");
assert!(result.is_err());
}
#[test]
fn parsed_path_nonexistent_nodes() {
let router = QueryRouter::new();
let result = router.execute_parsed("PATH SHORTEST 999999 -> 999998");
assert!(result.is_err());
}
#[test]
fn parsed_neighbors_nonexistent_node() {
let router = QueryRouter::new();
let result = router.execute_parsed("NEIGHBORS 999999 OUTGOING");
assert!(result.is_err());
}
#[test]
fn parsed_edge_get_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("EDGE GET 999999");
assert!(result.is_err());
}
#[test]
fn parsed_node_delete_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("NODE DELETE 999999");
assert!(result.is_err());
}
#[test]
fn parsed_embed_delete_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("EMBED DELETE 'nonexistent'");
assert!(result.is_err());
}
#[test]
fn parsed_select_nonexistent_table() {
let router = QueryRouter::new();
let result = router.execute_parsed("SELECT * FROM nonexistent");
assert!(result.is_err());
}
#[test]
fn parsed_update_nonexistent_table() {
let router = QueryRouter::new();
let result = router.execute_parsed("UPDATE nonexistent SET x = 1");
assert!(result.is_err());
}
#[test]
fn parsed_delete_nonexistent_table() {
let router = QueryRouter::new();
let result = router.execute_parsed("DELETE FROM nonexistent");
assert!(result.is_err());
}
#[test]
fn execute_only_whitespace() {
let router = QueryRouter::new();
let result = router.execute("\t\n \r\n");
assert!(result.is_err());
}
#[test]
fn parsed_embed_list() {
let router = QueryRouter::new();
router.execute("EMBED a [1.0]").unwrap();
router.execute("EMBED b [2.0]").unwrap();
let result = router.execute_parsed("EMBED LIST");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_insert_into_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("INSERT INTO nonexistent (x) VALUES (1)");
assert!(result.is_err());
}
#[test]
fn parsed_drop_nonexistent_table() {
let router = QueryRouter::new();
let result = router.execute_parsed("DROP TABLE nonexistent");
assert!(result.is_err());
}
#[test]
fn execute_tab_only() {
let router = QueryRouter::new();
let result = router.execute("\t");
assert!(result.is_err());
}
#[test]
fn parsed_embed_non_number_vector() {
let router = QueryRouter::new();
let result = router.execute_parsed("EMBED STORE 'k' ['a', 'b']");
assert!(result.is_err());
}
#[test]
fn parsed_similar_non_string_key() {
let router = QueryRouter::new();
let result = router.execute_parsed("SIMILAR [1,2,3] LIMIT 5");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_where_complex_column() {
let router = QueryRouter::new();
router.execute("CREATE TABLE t (x int)").unwrap();
let result = router.execute_parsed("SELECT * FROM t WHERE (1+2) = 3");
assert!(result.is_err());
}
#[test]
fn parsed_node_invalid_property_expr() {
let router = QueryRouter::new();
let result = router.execute_parsed("NODE CREATE test { val: (1+2) }");
assert!(result.is_err());
}
#[test]
fn show_tables_empty() {
let router = QueryRouter::new();
let result = router.execute_parsed("SHOW TABLES").unwrap();
match result {
QueryResult::TableList(tables) => {
assert!(tables.is_empty());
},
_ => panic!("Expected TableList"),
}
}
#[test]
fn show_tables_with_tables() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE users (id INT)")
.unwrap();
router
.execute_parsed("CREATE TABLE products (id INT)")
.unwrap();
let result = router.execute_parsed("SHOW TABLES").unwrap();
match result {
QueryResult::TableList(tables) => {
assert_eq!(tables.len(), 2);
assert!(tables.contains(&"users".to_string()));
assert!(tables.contains(&"products".to_string()));
},
_ => panic!("Expected TableList"),
}
}
#[test]
fn show_without_tables_error() {
let router = QueryRouter::new();
let result = router.execute_parsed("SHOW");
assert!(result.is_err());
}
#[test]
fn insert_without_columns() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE users (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO users VALUES (1, 'Alice')")
.unwrap();
router
.execute_parsed("INSERT INTO users VALUES (2, 'Bob')")
.unwrap();
let result = router.execute_parsed("SELECT * FROM users").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 2);
},
_ => panic!("Expected Rows"),
}
}
#[test]
fn with_shared_store_creates_unified_router() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
assert!(router.relational().list_tables().is_empty());
}
#[test]
fn with_shared_store_initializes_unified_engine() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
assert!(router.unified().is_some());
}
#[test]
fn new_router_has_unified_engine() {
let router = QueryRouter::new();
assert!(router.unified().is_some());
}
#[test]
fn unified_engine_delegates_find_neighbors_by_similarity() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
router
.vector()
.set_entity_embedding("center", vec![1.0, 0.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("neighbor1", vec![0.9, 0.1, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("neighbor2", vec![0.5, 0.5, 0.0])
.unwrap();
add_test_edge(router.graph(), "center", "neighbor1", "connected");
add_test_edge(router.graph(), "center", "neighbor2", "connected");
let query = vec![1.0, 0.0, 0.0];
let results = router
.find_neighbors_by_similarity("center", &query, 5)
.unwrap();
assert_eq!(results.len(), 2);
assert_eq!(results[0].id, "neighbor1");
assert!(results[0].score.unwrap() > results[1].score.unwrap());
}
#[test]
fn unified_engine_delegates_find_similar_connected() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
router
.vector()
.set_entity_embedding("query", vec![1.0, 0.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("connected1", vec![0.95, 0.05, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("connected2", vec![0.8, 0.2, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("not_connected", vec![0.99, 0.01, 0.0])
.unwrap();
add_test_edge(router.graph(), "hub", "connected1", "links");
add_test_edge(router.graph(), "hub", "connected2", "links");
let results = router.find_similar_connected("query", "hub", 5).unwrap();
assert!(results.len() <= 2);
for item in &results {
assert!(item.id == "connected1" || item.id == "connected2");
assert!(item.score.is_some());
}
}
#[test]
fn create_unified_entity_stores_embedding() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
let fields = HashMap::from([("name".to_string(), "Alice".to_string())]);
let embedding = vec![1.0, 0.0, 0.0];
router
.create_unified_entity("user:1", fields, Some(embedding.clone()))
.unwrap();
let retrieved = router.vector().get_entity_embedding("user:1").unwrap();
assert_eq!(retrieved, embedding);
}
#[test]
fn create_unified_entity_without_embedding() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
let fields = HashMap::from([("name".to_string(), "Alice".to_string())]);
router
.create_unified_entity("user:1", fields, None)
.unwrap();
assert!(!router.vector().entity_has_embedding("user:1"));
}
#[test]
fn connect_entities_creates_edge() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
let edge_key = router
.connect_entities("user:1", "user:2", "follows")
.unwrap();
assert!(edge_key.starts_with("edge:follows:"));
let neighbors = get_neighbors_out(router.graph(), "user:1");
assert_eq!(neighbors.len(), 1);
assert_eq!(neighbors[0], "user:2");
}
#[test]
fn find_similar_connected_returns_intersection() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
router
.vector()
.set_entity_embedding("query", vec![1.0, 0.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:1", vec![0.9, 0.1, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:2", vec![0.8, 0.2, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:3", vec![0.0, 1.0, 0.0])
.unwrap();
add_test_edge(router.graph(), "hub", "user:1", "connects");
add_test_edge(router.graph(), "hub", "user:2", "connects");
let results = router.find_similar_connected("query", "hub", 5).unwrap();
assert!(results.len() <= 2);
for item in &results {
assert!(item.id == "user:1" || item.id == "user:2");
assert!(item.score.is_some());
assert_eq!(item.source, "vector+graph");
}
}
#[test]
fn find_similar_connected_no_embedding() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
let result = router.find_similar_connected("nonexistent", "hub", 5);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn find_neighbors_by_similarity() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
router
.vector()
.set_entity_embedding("user:1", vec![1.0, 0.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:2", vec![0.0, 1.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:3", vec![0.5, 0.5, 0.0])
.unwrap();
add_test_edge(router.graph(), "center", "user:1", "knows");
add_test_edge(router.graph(), "center", "user:2", "knows");
add_test_edge(router.graph(), "center", "user:3", "knows");
let query = vec![1.0, 0.0, 0.0];
let results = router
.find_neighbors_by_similarity("center", &query, 3)
.unwrap();
assert_eq!(results.len(), 3);
assert_eq!(results[0].id, "user:1");
assert_eq!(results[0].source, "graph+vector");
}
#[test]
fn find_neighbors_by_similarity_no_entity() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
let result = router.find_neighbors_by_similarity("nonexistent", &[1.0, 0.0], 5);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn find_neighbors_by_similarity_filters_dimension_mismatch() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
router
.vector()
.set_entity_embedding("user:1", vec![1.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:2", vec![1.0, 0.0, 0.0])
.unwrap();
add_test_edge(router.graph(), "center", "user:1", "knows");
add_test_edge(router.graph(), "center", "user:2", "knows");
let query = vec![1.0, 0.0]; let results = router
.find_neighbors_by_similarity("center", &query, 5)
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "user:1");
}
#[test]
fn shared_store_engines_share_data() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
router
.vector()
.set_entity_embedding("entity:1", vec![1.0, 2.0])
.unwrap();
add_test_edge(router.graph(), "entity:1", "entity:2", "relates");
assert!(router.vector().entity_has_embedding("entity:1"));
assert!(entity_has_edges(router.graph(), "entity:1"));
}
#[test]
fn test_cache_init() {
let mut router = QueryRouter::new();
router.init_cache();
assert!(router.cache().is_some());
}
#[test]
fn test_cache_stats() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE STATS");
assert!(result.is_ok());
let output = unwrap_qr_value(result.unwrap());
assert!(output.contains("Cache Statistics"));
}
#[test]
fn test_cache_init_command() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE INIT");
assert!(result.is_ok());
let output = unwrap_qr_value(result.unwrap());
assert!(output.contains("Cache initialized"));
}
#[test]
fn test_cache_clear() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE CLEAR");
assert!(result.is_ok());
let output = unwrap_qr_value(result.unwrap());
assert!(output.contains("Cache cleared"));
}
#[test]
fn test_cache_without_init() {
let router = QueryRouter::new();
let result = router.execute_parsed("CACHE STATS");
assert!(result.is_err());
}
#[test]
fn test_cache_evict() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE EVICT");
assert!(result.is_ok());
let output = unwrap_qr_value(result.unwrap());
assert!(output.contains("Evicted"));
}
#[test]
fn test_cache_evict_with_count() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE EVICT 50");
assert!(result.is_ok());
let output = unwrap_qr_value(result.unwrap());
assert!(output.contains("Evicted"));
}
#[test]
fn test_cache_put_get() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE PUT 'testkey' 'testvalue'");
assert!(result.is_ok());
assert!(matches!(result.unwrap(), QueryResult::Value(s) if s == "OK"));
let result = router.execute_parsed("CACHE GET 'testkey'");
assert!(result.is_ok());
let output = unwrap_qr_value(result.unwrap());
assert_eq!(output, "testvalue");
}
#[test]
fn test_cache_get_not_found() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE GET 'nonexistent'");
assert!(result.is_ok());
let output = unwrap_qr_value(result.unwrap());
assert_eq!(output, "(not found)");
}
#[test]
#[ignore] fn test_query_cache_select() {
let mut router = QueryRouter::new();
router.init_cache();
router
.execute_parsed("CREATE TABLE cached_test (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO cached_test (id, name) VALUES (1, 'Alice')")
.unwrap();
let result1 = router.execute_parsed("SELECT * FROM cached_test").unwrap();
assert!(matches!(result1, QueryResult::Rows(_)));
let result2 = router.execute_parsed("SELECT * FROM cached_test").unwrap();
assert!(matches!(result2, QueryResult::Rows(_)));
let stats = router.cache.as_ref().unwrap().stats();
assert!(stats.hits(CacheLayer::Exact) > 0);
}
#[test]
#[ignore] fn test_query_cache_invalidation() {
let mut router = QueryRouter::new();
router.init_cache();
router
.execute_parsed("CREATE TABLE invalidate_test (id INT)")
.unwrap();
router
.execute_parsed("INSERT INTO invalidate_test (id) VALUES (1)")
.unwrap();
let _ = router.execute_parsed("SELECT * FROM invalidate_test");
let _hits_before = router
.cache
.as_ref()
.unwrap()
.stats()
.hits(CacheLayer::Exact);
router
.execute_parsed("INSERT INTO invalidate_test (id) VALUES (2)")
.unwrap();
let _ = router.execute_parsed("SELECT * FROM invalidate_test");
let misses_after = router
.cache
.as_ref()
.unwrap()
.stats()
.misses(CacheLayer::Exact);
assert!(misses_after > 0);
}
#[test]
fn test_is_write_statement_sql_writes() {
let cases = [
("INSERT INTO t (x) VALUES (1)", true),
("UPDATE t SET x = 1", true),
("DELETE FROM t WHERE x = 1", true),
("CREATE TABLE t (id INT)", true),
("DROP TABLE t", true),
("CREATE INDEX idx ON t (x)", true),
("DROP INDEX ON t(x)", true),
];
for (query, expected) in &cases {
let stmt = parser::parse(query).unwrap();
assert_eq!(
QueryRouter::is_write_statement(&stmt),
*expected,
"Failed for: {query}"
);
}
}
#[test]
fn test_is_write_statement_reads_are_false() {
let cases = [
"SELECT * FROM t",
"SHOW TABLES",
"DESCRIBE TABLE t",
"SHOW EMBEDDINGS",
];
for query in &cases {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Should be false for: {query}"
);
}
}
#[test]
fn test_is_write_statement_graph_ops() {
let writes = [
"NODE CREATE label1 { name: 'test' }",
"NODE DELETE 1",
"EDGE CREATE 1 -> 2 : knows",
"EDGE DELETE 1",
];
for query in &writes {
let stmt = parser::parse(query).unwrap();
assert!(
QueryRouter::is_write_statement(&stmt),
"Should be write: {query}"
);
}
let reads = ["NODE GET 1", "EDGE GET 1", "NODE LIST", "EDGE LIST"];
for query in &reads {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Should be read: {query}"
);
}
}
#[test]
fn test_is_write_statement_embed_ops() {
let writes = [
"EMBED STORE 'key1' [1.0, 2.0, 3.0]",
"EMBED DELETE 'key1'",
"EMBED BATCH [('a', [1.0, 2.0]), ('b', [3.0, 4.0])]",
];
for query in &writes {
let stmt = parser::parse(query).unwrap();
assert!(
QueryRouter::is_write_statement(&stmt),
"Should be write: {query}"
);
}
let reads = ["EMBED GET 'key1'"];
for query in &reads {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Should be read: {query}"
);
}
}
#[test]
fn test_is_write_statement_entity_ops() {
let writes = [
"ENTITY CREATE 'e1' { name: 'test' }",
"ENTITY UPDATE 'e1' { name: 'updated' }",
"ENTITY DELETE 'e1'",
"ENTITY CONNECT 'e1' -> 'e2' : related",
];
for query in &writes {
let stmt = parser::parse(query).unwrap();
assert!(
QueryRouter::is_write_statement(&stmt),
"Should be write: {query}"
);
}
let reads = ["ENTITY GET 'e1'"];
for query in &reads {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Should be read: {query}"
);
}
}
#[test]
fn test_is_write_statement_cache_never_invalidates() {
let cases = [
"CACHE PUT 'key' 'value'",
"CACHE GET 'key'",
"CACHE CLEAR",
"CACHE STATS",
];
for query in &cases {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Cache should never invalidate: {query}"
);
}
}
#[test]
fn test_is_write_statement_rollback_is_write() {
let stmt = parser::parse("ROLLBACK TO 'checkpoint_id'").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_checkpoint_is_not_write() {
let stmt = parser::parse("CHECKPOINT 'snap1'").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_spatial_ops() {
let writes = [
"SPATIAL INSERT 'loc1' BOUNDS 10 20 30 40",
"SPATIAL DELETE 'loc1' BOUNDS 10 20 30 40",
];
for query in &writes {
let stmt = parser::parse(query).unwrap();
assert!(
QueryRouter::is_write_statement(&stmt),
"Should be write: {query}"
);
}
let reads = [
"SPATIAL WITHIN 5.0 10.0 RADIUS 25.0",
"SPATIAL NEAREST 5.0 10.0 LIMIT 3",
];
for query in &reads {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Should be read: {query}"
);
}
}
#[test]
fn test_is_write_statement_entity_batch() {
let stmt =
parser::parse("ENTITY BATCH CREATE [{key: 'e1', name: 'a'}, {key: 'e2', name: 'b'}]")
.unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_entity_get_is_read() {
let stmt = parser::parse("ENTITY GET 'e1'").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_vault_ops() {
let writes = ["VAULT SET 'secret' 'value'", "VAULT DELETE 'secret'"];
for query in &writes {
let stmt = parser::parse(query).unwrap();
assert!(
QueryRouter::is_write_statement(&stmt),
"Should be write: {query}"
);
}
let reads = ["VAULT GET 'secret'", "VAULT LIST"];
for query in &reads {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Should be read: {query}"
);
}
}
#[test]
fn test_is_write_statement_blob_ops() {
let writes = [
"BLOB PUT 'test.txt' FROM '/tmp/test.txt'",
"BLOB DELETE 'abc123'",
];
for query in &writes {
let stmt = parser::parse(query).unwrap();
assert!(
QueryRouter::is_write_statement(&stmt),
"Should be write: {query}"
);
}
let reads = ["BLOB GET 'abc123'"];
for query in &reads {
let stmt = parser::parse(query).unwrap();
assert!(
!QueryRouter::is_write_statement(&stmt),
"Should be read: {query}"
);
}
}
#[test]
#[ignore] fn test_query_cache_case_insensitive() {
let mut router = QueryRouter::new();
router.init_cache();
router
.execute_parsed("CREATE TABLE case_test (id INT)")
.unwrap();
router
.execute_parsed("INSERT INTO case_test (id) VALUES (1)")
.unwrap();
let _ = router.execute_parsed("SELECT * FROM case_test");
let _ = router.execute_parsed("select * from case_test");
let stats = router.cache.as_ref().unwrap().stats();
assert!(stats.hits(CacheLayer::Exact) > 0);
}
#[test]
fn test_vault_not_initialized() {
let router = QueryRouter::new();
let result = router.execute_parsed("VAULT SET 'key' 'value'");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("not initialized"));
}
#[test]
fn test_vault_set_get() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
router.set_identity(Vault::ROOT);
router
.execute_parsed("VAULT SET 'secret_key' 'secret_value'")
.unwrap();
let result = router.execute_parsed("VAULT GET 'secret_key'").unwrap();
match result {
QueryResult::Value(v) => assert_eq!(v, "secret_value"),
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_vault_delete() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
router.set_identity(Vault::ROOT);
router
.execute_parsed("VAULT SET 'to_delete' 'value'")
.unwrap();
router.execute_parsed("VAULT DELETE 'to_delete'").unwrap();
let result = router.execute_parsed("VAULT GET 'to_delete'");
assert!(result.is_err());
}
#[test]
fn test_vault_list() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
router.set_identity(Vault::ROOT);
router.execute_parsed("VAULT SET 'key1' 'v1'").unwrap();
router.execute_parsed("VAULT SET 'key2' 'v2'").unwrap();
let result = router.execute_parsed("VAULT LIST").unwrap();
match result {
QueryResult::Value(v) => {
assert!(v.contains("key1"));
assert!(v.contains("key2"));
},
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_vault_list_with_pattern() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
router.set_identity(Vault::ROOT);
router.execute_parsed("VAULT SET 'db_pass' 'v1'").unwrap();
router.execute_parsed("VAULT SET 'db_user' 'v2'").unwrap();
router.execute_parsed("VAULT SET 'api_key' 'v3'").unwrap();
let result = router.execute_parsed("VAULT LIST 'db_*'").unwrap();
match result {
QueryResult::Value(v) => {
assert!(v.contains("db_pass"));
assert!(v.contains("db_user"));
},
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_vault_rotate() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
router.set_identity(Vault::ROOT);
router
.execute_parsed("VAULT SET 'rotate_key' 'old_value'")
.unwrap();
router
.execute_parsed("VAULT ROTATE 'rotate_key' 'new_value'")
.unwrap();
let result = router.execute_parsed("VAULT GET 'rotate_key'").unwrap();
match result {
QueryResult::Value(v) => assert_eq!(v, "new_value"),
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_vault_grant_revoke() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
router.set_identity(Vault::ROOT);
router
.execute_parsed("VAULT SET 'shared_key' 'shared_value'")
.unwrap();
let grant_result = router.execute_parsed("VAULT GRANT 'user:bob' 'shared_key'");
assert!(grant_result.is_ok() || grant_result.is_err());
let revoke_result = router.execute_parsed("VAULT REVOKE 'user:bob' 'shared_key'");
assert!(revoke_result.is_ok() || revoke_result.is_err());
}
#[test]
fn test_blob_not_initialized() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB PUT 'test.txt' 'hello'");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("not initialized"));
}
#[test]
fn test_blob_put_get_delete() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'test.txt' 'Hello, World!'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result with artifact ID"),
};
let get_result = router
.execute_parsed(&format!("BLOB GET '{}'", artifact_id))
.unwrap();
match get_result {
QueryResult::Blob(data) => {
assert_eq!(String::from_utf8_lossy(&data), "Hello, World!");
},
_ => panic!("Expected Blob result"),
}
router
.execute_parsed(&format!("BLOB DELETE '{}'", artifact_id))
.unwrap();
let get_after_delete = router.execute_parsed(&format!("BLOB GET '{}'", artifact_id));
assert!(get_after_delete.is_err());
}
#[test]
fn test_blob_info() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'info_test.txt' 'test data'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let info_result = router
.execute_parsed(&format!("BLOB INFO '{}'", artifact_id))
.unwrap();
match info_result {
QueryResult::ArtifactInfo(info) => {
assert_eq!(info.filename, "info_test.txt");
assert_eq!(info.size, 9);
},
_ => panic!("Expected ArtifactInfo result"),
}
}
#[test]
fn test_blob_link_unlink() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'link_test.txt' 'data'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
router
.execute_parsed(&format!("BLOB LINK '{}' TO 'task:123'", artifact_id))
.unwrap();
let links_result = router
.execute_parsed(&format!("BLOB LINKS '{}'", artifact_id))
.unwrap();
match links_result {
QueryResult::ArtifactList(links) => {
assert!(links.contains(&"task:123".to_string()));
},
_ => panic!("Expected ArtifactList result"),
}
router
.execute_parsed(&format!("BLOB UNLINK '{}' FROM 'task:123'", artifact_id))
.unwrap();
}
#[test]
fn test_blob_tag_untag() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'tag_test.txt' 'data'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
router
.execute_parsed(&format!("BLOB TAG '{}' 'important'", artifact_id))
.unwrap();
let info = router
.execute_parsed(&format!("BLOB INFO '{}'", artifact_id))
.unwrap();
match info {
QueryResult::ArtifactInfo(info) => {
assert!(info.tags.contains(&"important".to_string()));
},
_ => panic!("Expected ArtifactInfo"),
}
router
.execute_parsed(&format!("BLOB UNTAG '{}' 'important'", artifact_id))
.unwrap();
}
#[test]
fn test_blob_verify() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'verify_test.txt' 'verify me'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let verify_result = router
.execute_parsed(&format!("BLOB VERIFY '{}'", artifact_id))
.unwrap();
match verify_result {
QueryResult::Value(v) => assert_eq!(v, "OK"),
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_blob_gc() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let gc_result = router.execute_parsed("BLOB GC").unwrap();
match gc_result {
QueryResult::Value(v) => {
assert!(v.contains("Deleted"));
assert!(v.contains("freed"));
},
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_blob_gc_full() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let gc_result = router.execute_parsed("BLOB GC FULL").unwrap();
match gc_result {
QueryResult::Value(v) => {
assert!(v.contains("Deleted"));
assert!(v.contains("freed"));
},
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_blob_repair() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let repair_result = router.execute_parsed("BLOB REPAIR").unwrap();
match repair_result {
QueryResult::Value(v) => {
assert!(v.contains("Fixed"));
assert!(v.contains("orphans"));
},
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_blob_stats() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let stats_result = router.execute_parsed("BLOB STATS").unwrap();
match stats_result {
QueryResult::BlobStats(stats) => {
assert_eq!(stats.artifact_count, 0);
},
_ => panic!("Expected BlobStats result"),
}
}
#[test]
fn test_blob_meta_set_get() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'meta_test.txt' 'data'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
router
.execute_parsed(&format!("BLOB META SET '{}' 'author' 'alice'", artifact_id))
.unwrap();
let meta_result = router
.execute_parsed(&format!("BLOB META GET '{}' 'author'", artifact_id))
.unwrap();
match meta_result {
QueryResult::Value(v) => assert_eq!(v, "alice"),
_ => panic!("Expected Value result"),
}
let missing_meta = router
.execute_parsed(&format!("BLOB META GET '{}' 'nonexistent'", artifact_id))
.unwrap();
match missing_meta {
QueryResult::Value(v) => assert_eq!(v, "(not found)"),
_ => panic!("Expected Value result"),
}
}
#[test]
fn test_blob_put_missing_data() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB PUT 'missing.txt'");
assert!(result.is_err());
}
#[test]
fn test_blobs_list() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
router
.execute_parsed("BLOB PUT 'file1.txt' 'data1'")
.unwrap();
router
.execute_parsed("BLOB PUT 'file2.txt' 'data2'")
.unwrap();
let list_result = router.execute_parsed("BLOBS").unwrap();
match list_result {
QueryResult::ArtifactList(list) => {
assert_eq!(list.len(), 2);
},
_ => panic!("Expected ArtifactList result"),
}
}
#[test]
fn test_blobs_list_with_pattern() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let list_result = router.execute_parsed("BLOBS 'some_prefix'");
match list_result {
Ok(QueryResult::ArtifactList(_)) => {},
_ => panic!("Expected ArtifactList result"),
}
}
#[test]
fn test_blobs_find_by_link() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'linked.txt' 'data'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
router
.execute_parsed(&format!("BLOB LINK '{}' TO 'project:alpha'", artifact_id))
.unwrap();
let find_result = router.execute_parsed("BLOBS FOR 'project:alpha'").unwrap();
match find_result {
QueryResult::ArtifactList(list) => {
assert!(!list.is_empty());
},
_ => panic!("Expected ArtifactList result"),
}
}
#[test]
fn test_blobs_find_by_tag() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'tagged.txt' 'data'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
router
.execute_parsed(&format!("BLOB TAG '{}' 'urgent'", artifact_id))
.unwrap();
let find_result = router.execute_parsed("BLOBS BY TAG 'urgent'").unwrap();
match find_result {
QueryResult::ArtifactList(list) => {
assert!(!list.is_empty());
},
_ => panic!("Expected ArtifactList result"),
}
}
#[test]
fn test_blobs_not_initialized() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let result = router.execute_parsed("BLOBS LIST");
assert!(result.is_err());
}
#[test]
fn test_vault_get_not_found() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
router.set_identity(Vault::ROOT);
let result = router.execute_parsed("VAULT GET 'nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_blob_get_not_found() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB GET 'artifact:nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_blob_delete_not_found() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB DELETE 'artifact:nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_blob_info_not_found() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB INFO 'artifact:nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_blob_verify_not_found() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB VERIFY 'artifact:nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_start_blob_not_initialized() {
let mut router = QueryRouter::new();
let result = router.start_blob();
assert!(result.is_err());
}
#[test]
fn test_blob_put_with_options() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router
.execute_parsed("BLOB PUT 'options_test.txt' 'data' LINK 'task:123' TAG 'important'");
assert!(result.is_ok());
let artifact_id = match result.unwrap() {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let links = router
.execute_parsed(&format!("BLOB LINKS '{}'", artifact_id))
.unwrap();
match links {
QueryResult::ArtifactList(l) => {
assert!(l.contains(&"task:123".to_string()));
},
_ => panic!("Expected ArtifactList"),
}
let info = router
.execute_parsed(&format!("BLOB INFO '{}'", artifact_id))
.unwrap();
match info {
QueryResult::ArtifactInfo(i) => {
assert!(i.tags.contains(&"important".to_string()));
},
_ => panic!("Expected ArtifactInfo"),
}
}
#[test]
fn test_blobs_similar() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOBS SIMILAR TO 'artifact:test' LIMIT 5");
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_blobs_for_entity() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOBS FOR 'task:123'");
match result {
Ok(QueryResult::ArtifactList(_)) => {},
_ => panic!("Expected ArtifactList result"),
}
}
#[test]
fn test_blobs_by_type() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOBS WHERE TYPE = 'text/plain'");
match result {
Ok(QueryResult::ArtifactList(_)) => {},
_ => panic!("Expected ArtifactList result"),
}
}
#[test]
fn test_shutdown_blob() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.shutdown_blob();
assert!(result.is_ok());
}
#[test]
fn test_shutdown_blob_not_initialized() {
let mut router = QueryRouter::new();
let result = router.shutdown_blob();
assert!(result.is_ok());
}
#[test]
fn test_set_identity() {
let mut router = QueryRouter::new();
assert_eq!(router.current_identity(), None);
assert!(!router.is_authenticated());
router.set_identity("user:alice");
assert_eq!(router.current_identity(), Some("user:alice"));
assert!(router.is_authenticated());
}
#[test]
fn test_vault_requires_authentication() {
let mut router = QueryRouter::new();
router.init_vault(b"test_master_key_32bytes!").unwrap();
let result = router.execute_parsed("VAULT GET 'api_key'");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RouterError::AuthenticationRequired),
"Expected AuthenticationRequired, got: {:?}",
err
);
router.set_identity("user:alice");
let result = router.execute_parsed("VAULT GET 'nonexistent_key'");
match result {
Err(RouterError::AuthenticationRequired) => {
panic!("Should not get AuthenticationRequired after set_identity")
},
_ => {}, }
}
#[test]
fn test_cache_requires_authentication() {
let mut router = QueryRouter::new();
router.init_cache_default().unwrap();
let result = router.execute_parsed("CACHE STATS");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RouterError::AuthenticationRequired),
"Expected AuthenticationRequired, got: {:?}",
err
);
router.set_identity("user:test");
let result = router.execute_parsed("CACHE STATS");
if let Err(RouterError::AuthenticationRequired) = result {
panic!("Should not get AuthenticationRequired after set_identity")
}
}
#[test]
fn test_blob_requires_authentication() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB INIT");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RouterError::AuthenticationRequired),
"Expected AuthenticationRequired, got: {:?}",
err
);
router.set_identity("user:test");
let result = router.execute_parsed("BLOB INIT");
if let Err(RouterError::AuthenticationRequired) = result {
panic!("Should not get AuthenticationRequired after set_identity")
}
}
#[test]
fn test_blobs_requires_authentication() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOBS");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RouterError::AuthenticationRequired),
"Expected AuthenticationRequired, got: {:?}",
err
);
router.set_identity("user:test");
let result = router.execute_parsed("BLOBS");
if let Err(RouterError::AuthenticationRequired) = result {
panic!("Should not get AuthenticationRequired after set_identity")
}
}
#[test]
fn test_chain_requires_authentication() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
let result = router.execute_parsed("CHAIN HEIGHT");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RouterError::AuthenticationRequired),
"Expected AuthenticationRequired, got: {:?}",
err
);
router.set_identity("user:test");
let result = router.execute_parsed("CHAIN HEIGHT");
if let Err(RouterError::AuthenticationRequired) = result {
panic!("Should not get AuthenticationRequired after set_identity")
}
}
#[test]
fn test_init_cache_default() {
let mut router = QueryRouter::new();
let result = router.init_cache_default();
assert!(result.is_ok());
assert!(router.cache().is_some());
}
#[test]
fn test_init_cache_with_config() {
let mut router = QueryRouter::new();
let config = tensor_cache::CacheConfig::default();
let _ = router.init_cache_with_config(config);
assert!(router.cache().is_some());
}
#[test]
fn test_blob_accessor() {
let mut router = QueryRouter::new();
assert!(router.blob().is_none());
router.init_blob().unwrap();
assert!(router.blob().is_some());
}
#[test]
fn test_error_display_all_variants() {
let errors = vec![
RouterError::ParseError("parse msg".to_string()),
RouterError::UnknownCommand("unknown".to_string()),
RouterError::RelationalError("rel msg".to_string()),
RouterError::GraphError("graph msg".to_string()),
RouterError::VectorError("vec msg".to_string()),
RouterError::VaultError("vault msg".to_string()),
RouterError::CacheError("cache msg".to_string()),
RouterError::BlobError("blob msg".to_string()),
RouterError::InvalidArgument("invalid msg".to_string()),
RouterError::TypeMismatch("type msg".to_string()),
RouterError::MissingArgument("missing msg".to_string()),
];
for e in errors {
let display = format!("{}", e);
assert!(!display.is_empty());
}
}
#[test]
fn test_blob_from_path() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB PUT 'from_path.txt' FROM '/nonexistent/path'");
assert!(result.is_err());
}
#[test]
fn test_blob_get_to_path() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'get_to.txt' 'test data'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let result = router.execute_parsed(&format!(
"BLOB GET '{}' TO '/nonexistent/dir/file.txt'",
artifact_id
));
assert!(result.is_err());
}
#[test]
fn test_init_vault() {
let mut router = QueryRouter::new();
let result = router.init_vault(b"32_byte_master_key_for_testing!");
assert!(result.is_ok());
}
#[test]
fn test_vault_rotate_nonexistent() {
let mut router = QueryRouter::new();
router
.init_vault(b"32_byte_master_key_for_testing!")
.unwrap();
router.set_identity(Vault::ROOT);
let result = router.execute_parsed("VAULT ROTATE 'nonexistent' 'new_value'");
assert!(result.is_err());
}
#[test]
fn test_vault_delete_nonexistent() {
let mut router = QueryRouter::new();
router
.init_vault(b"32_byte_master_key_for_testing!")
.unwrap();
router.set_identity(Vault::ROOT);
let result = router.execute_parsed("VAULT DELETE 'nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_blob_link_nonexistent() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB LINK 'nonexistent' TO 'entity'");
assert!(result.is_err());
}
#[test]
fn test_blob_unlink_nonexistent() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB UNLINK 'nonexistent' FROM 'entity'");
assert!(result.is_err());
}
#[test]
fn test_blob_tag_nonexistent() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB TAG 'nonexistent' 'tag'");
assert!(result.is_err());
}
#[test]
fn test_blob_untag_nonexistent() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB UNTAG 'nonexistent' 'tag'");
assert!(result.is_err());
}
#[test]
fn test_blob_links_nonexistent() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB LINKS 'nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_blob_meta_set_nonexistent() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB META SET 'nonexistent' 'key' 'value'");
assert!(result.is_err());
}
#[test]
fn test_blob_meta_get_nonexistent() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.execute_parsed("BLOB META GET 'nonexistent' 'key'");
assert!(result.is_err());
}
#[test]
fn test_blob_get_to_valid_path() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let put_result = router
.execute_parsed("BLOB PUT 'get_to_valid.txt' 'test'")
.unwrap();
let artifact_id = match put_result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir.join("neumann_test_blob_output.txt");
let temp_str = temp_path.to_string_lossy().replace('\\', "/");
let result = router.execute_parsed(&format!("BLOB GET '{artifact_id}' TO '{temp_str}'"));
assert!(result.is_ok(), "BLOB GET TO failed: {result:?}");
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_find_similar_connected_no_embedding() {
let router = QueryRouter::new();
let result = router.find_similar_connected("nonexistent", "other", 5);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_query_result_debug() {
let result = QueryResult::Empty;
let debug_str = format!("{:?}", result);
assert!(!debug_str.is_empty());
let result = QueryResult::Value("test".to_string());
let debug_str = format!("{:?}", result);
assert!(debug_str.contains("test"));
}
#[test]
fn test_error_from_conversions() {
let rel_err = relational_engine::RelationalError::TableNotFound("test".to_string());
let router_err: RouterError = rel_err.into();
assert!(matches!(router_err, RouterError::RelationalError(_)));
let graph_err = graph_engine::GraphError::NodeNotFound(1);
let router_err: RouterError = graph_err.into();
assert!(matches!(router_err, RouterError::GraphError(_)));
let vec_err = vector_engine::VectorError::NotFound("test".to_string());
let router_err: RouterError = vec_err.into();
assert!(matches!(router_err, RouterError::VectorError(_)));
}
#[test]
fn parsed_entity_create_basic() {
let router = QueryRouter::new();
let result = router.execute_parsed("ENTITY CREATE 'user:1' { name: 'Alice' }");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(msg) => {
assert!(msg.contains("Entity 'user:1' created"));
},
_ => panic!("expected Value result"),
}
}
#[test]
fn parsed_entity_create_with_embedding() {
let router = QueryRouter::new();
let result =
router.execute_parsed("ENTITY CREATE 'doc:1' { title: 'Test' } EMBEDDING [1.0, 0.0]");
assert!(result.is_ok());
let emb = router.vector().get_entity_embedding("doc:1");
assert!(emb.is_ok());
assert_eq!(emb.unwrap(), vec![1.0, 0.0]);
}
#[test]
fn parsed_entity_connect() {
let router = QueryRouter::new();
let result = router.execute_parsed("ENTITY CONNECT 'user:1' -> 'user:2' : follows");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(msg) => {
assert!(msg.contains("Connected 'user:1' -> 'user:2'"));
},
_ => panic!("expected Value result"),
}
}
#[test]
fn parsed_similar_connected_to() {
let router = QueryRouter::new();
router
.vector()
.set_entity_embedding("query", vec![1.0, 0.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:1", vec![0.9, 0.1, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:2", vec![0.8, 0.2, 0.0])
.unwrap();
add_test_edge(router.graph(), "hub", "user:1", "connects");
add_test_edge(router.graph(), "hub", "user:2", "connects");
let result = router.execute_parsed("SIMILAR 'query' CONNECTED TO 'hub' LIMIT 5");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Similar(results) => {
assert!(!results.is_empty());
},
_ => panic!("expected Similar result"),
}
}
#[test]
fn parsed_similar_connected_to_requires_key() {
let router = QueryRouter::new();
let result = router.execute_parsed("SIMILAR [1.0, 0.0] CONNECTED TO 'hub'");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("requires a key"));
}
#[test]
fn parsed_neighbors_by_similarity() {
let router = QueryRouter::new();
router
.vector()
.set_entity_embedding("user:1", vec![1.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:2", vec![0.0, 1.0])
.unwrap();
add_test_edge(router.graph(), "center", "user:1", "knows");
add_test_edge(router.graph(), "center", "user:2", "knows");
let result = router.execute_parsed("NEIGHBORS 'center' BY SIMILAR [1.0, 0.0] LIMIT 5");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Similar(results) => {
assert!(!results.is_empty());
assert_eq!(results[0].key, "user:1");
},
_ => panic!("expected Similar result"),
}
}
#[test]
fn parsed_entity_create_empty_properties() {
let router = QueryRouter::new();
let result = router.execute_parsed("ENTITY CREATE 'empty:1' {}");
assert!(result.is_ok());
}
#[test]
fn parsed_entity_create_multiple_properties() {
let router = QueryRouter::new();
let result =
router.execute_parsed("ENTITY CREATE 'user:2' { name: 'Bob', age: 30, active: true }");
assert!(result.is_ok());
}
#[test]
fn parser_entity_statement() {
let result = parser::parse("ENTITY CREATE 'key' { prop: 'value' }");
assert!(result.is_ok());
let stmt = result.unwrap();
assert!(matches!(stmt.kind, StatementKind::Entity(_)));
}
#[test]
fn parser_entity_connect_statement() {
let result = parser::parse("ENTITY CONNECT 'from' -> 'to' : type");
assert!(result.is_ok());
let stmt = result.unwrap();
if let StatementKind::Entity(entity) = stmt.kind {
assert!(matches!(entity.operation, EntityOp::Connect { .. }));
} else {
panic!("expected Entity statement");
}
}
#[test]
fn parser_similar_connected_to() {
let result = parser::parse("SIMILAR 'key' CONNECTED TO 'hub' LIMIT 10");
assert!(result.is_ok());
let stmt = result.unwrap();
if let StatementKind::Similar(similar) = stmt.kind {
assert!(similar.connected_to.is_some());
} else {
panic!("expected Similar statement");
}
}
#[test]
fn parser_neighbors_by_similarity() {
let result = parser::parse("NEIGHBORS 'entity' BY SIMILAR [1.0, 0.0] LIMIT 5");
assert!(result.is_ok());
let stmt = result.unwrap();
if let StatementKind::Neighbors(neighbors) = stmt.kind {
assert!(neighbors.by_similarity.is_some());
assert!(neighbors.limit.is_some());
} else {
panic!("expected Neighbors statement");
}
}
#[test]
fn parsed_drop_index_on_table_column() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE products (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("CREATE INDEX idx_name ON products(name)")
.unwrap();
assert!(router.relational().has_index("products", "name"));
let result = router.execute_parsed("DROP INDEX ON products(name)");
assert!(result.is_ok());
assert!(!router.relational().has_index("products", "name"));
}
#[test]
fn parsed_drop_index_if_exists() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT)")
.unwrap();
let result = router.execute_parsed("DROP INDEX IF EXISTS ON items(id)");
assert!(result.is_ok());
}
#[test]
fn parsed_drop_index_not_found() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE data (col INT)")
.unwrap();
let result = router.execute_parsed("DROP INDEX ON data(col)");
assert!(result.is_err());
}
#[test]
fn parsed_drop_index_named_not_supported() {
let router = QueryRouter::new();
let result = router.execute_parsed("DROP INDEX my_index");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("not supported"));
}
#[test]
fn parser_drop_index_on_syntax() {
let result = parser::parse("DROP INDEX ON users(email)");
assert!(result.is_ok());
let stmt = result.unwrap();
if let StatementKind::DropIndex(drop) = stmt.kind {
assert!(drop.table.is_some());
assert_eq!(drop.table.unwrap().name, "users");
assert!(drop.column.is_some());
assert_eq!(drop.column.unwrap().name, "email");
} else {
panic!("expected DropIndex");
}
}
#[test]
fn parser_drop_index_if_exists_on() {
let result = parser::parse("DROP INDEX IF EXISTS ON products(sku)");
assert!(result.is_ok());
let stmt = result.unwrap();
if let StatementKind::DropIndex(drop) = stmt.kind {
assert!(drop.if_exists);
assert!(drop.table.is_some());
} else {
panic!("expected DropIndex");
}
}
#[test]
fn parsed_insert_select_with_where() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE employees (id INT, dept TEXT)")
.unwrap();
router
.execute_parsed("CREATE TABLE engineers (id INT, dept TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO employees VALUES (1, 'eng')")
.unwrap();
router
.execute_parsed("INSERT INTO employees VALUES (2, 'sales')")
.unwrap();
router
.execute_parsed("INSERT INTO employees VALUES (3, 'eng')")
.unwrap();
let result = router
.execute_parsed("INSERT INTO engineers SELECT * FROM employees WHERE dept = 'eng'");
assert!(result.is_ok());
let rows = router.execute_parsed("SELECT * FROM engineers").unwrap();
match rows {
QueryResult::Rows(r) => {
assert_eq!(r.len(), 2);
},
_ => panic!("expected Rows"),
}
}
#[test]
fn parsed_insert_select_empty_result() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE source (id INT)")
.unwrap();
router
.execute_parsed("CREATE TABLE target (id INT)")
.unwrap();
let result =
router.execute_parsed("INSERT INTO target SELECT * FROM source WHERE id > 100");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Ids(ids) => {
assert!(ids.is_empty());
},
_ => panic!("expected Ids"),
}
}
#[test]
fn parsed_insert_select_with_columns() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE complete (id INT, name TEXT, age INT)")
.unwrap();
router
.execute_parsed("CREATE TABLE partial (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO complete VALUES (1, 'Alice', 30)")
.unwrap();
let result =
router.execute_parsed("INSERT INTO partial (id, name) SELECT id, name FROM complete");
assert!(result.is_ok());
let rows = router.execute_parsed("SELECT * FROM partial").unwrap();
match rows {
QueryResult::Rows(r) => {
assert_eq!(r.len(), 1);
},
_ => panic!("expected Rows"),
}
}
#[test]
fn parsed_blob_init_not_initialized() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB INIT");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.to_string().contains("init_blob"),
"should mention init_blob()"
);
}
#[test]
fn parsed_blob_init_already_initialized() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB INIT");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert!(
v.contains("already initialized"),
"should say already initialized"
);
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_embed_build_index_not_built() {
let router = QueryRouter::new();
let result = router.execute_parsed("EMBED BUILD INDEX");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.to_string().contains("build_vector_index"),
"should mention build_vector_index()"
);
}
#[test]
fn parsed_embed_build_index_already_built() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'key1' [1.0, 0.0]")
.unwrap();
router
.execute_parsed("EMBED STORE 'key2' [0.0, 1.0]")
.unwrap();
router.build_vector_index().unwrap();
let result = router.execute_parsed("EMBED BUILD INDEX");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert!(v.contains("already built"), "should say already built");
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_embed_batch_basic() {
let router = QueryRouter::new();
let result = router.execute_parsed(
"EMBED BATCH [('doc1', [1.0, 0.0]), ('doc2', [0.0, 1.0]), ('doc3', [0.5, 0.5])]",
);
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Count(n) => {
assert_eq!(n, 3, "should store 3 embeddings");
},
_ => panic!("expected Count"),
}
let result = router.execute_parsed("EMBED GET 'doc1'");
assert!(result.is_ok());
}
#[test]
fn parsed_embed_batch_empty() {
let router = QueryRouter::new();
let result = router.execute_parsed("EMBED BATCH []");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Count(n) => {
assert_eq!(n, 0, "empty batch should return 0");
},
_ => panic!("expected Count"),
}
}
#[test]
fn parsed_cache_semantic_put() {
let mut router = QueryRouter::new();
let mut config = CacheConfig::default();
config.embedding_dim = 3;
let _ = router.init_cache_with_config(config);
router.set_identity("user:test");
let result = router.execute_parsed(
"CACHE SEMANTIC PUT 'What is 2+2?' 'The answer is 4' EMBEDDING [1.0, 0.0, 0.0]",
);
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert_eq!(v, "OK");
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_cache_semantic_get() {
let mut router = QueryRouter::new();
let mut config = CacheConfig::default();
config.embedding_dim = 2;
let _ = router.init_cache_with_config(config);
router.set_identity("user:test");
router
.execute_parsed("CACHE SEMANTIC PUT 'hello' 'world' EMBEDDING [1.0, 0.0]")
.unwrap();
router
.execute_parsed("EMBED STORE 'hello' [1.0, 0.0]")
.unwrap();
let result = router.execute_parsed("CACHE SEMANTIC GET 'hello'");
assert!(result.is_ok());
}
#[test]
fn parsed_cache_semantic_get_with_threshold() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE SEMANTIC GET 'unknown query' THRESHOLD 0.9");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert!(v.contains("not found"));
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_describe_table() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE users (id INT NOT NULL, name TEXT, active BOOLEAN)")
.unwrap();
let result = router.execute_parsed("DESCRIBE TABLE users");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert!(v.contains("Table: users"));
assert!(v.contains("id"));
assert!(v.contains("name"));
assert!(v.contains("active"));
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_describe_node() {
let router = QueryRouter::new();
router
.execute_parsed("NODE CREATE person {name: 'Alice'}")
.unwrap();
let result = router.execute_parsed("DESCRIBE NODE person");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert!(v.contains("Node label 'person'"));
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_describe_edge() {
let router = QueryRouter::new();
let result = router.execute_parsed("DESCRIBE EDGE follows");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert!(v.contains("Edge type 'follows'"));
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_show_embeddings() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'emb1' [1.0, 0.0]")
.unwrap();
router
.execute_parsed("EMBED STORE 'emb2' [0.0, 1.0]")
.unwrap();
let result = router.execute_parsed("SHOW EMBEDDINGS");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Value(v) => {
assert!(v.contains("emb1") || v.contains("emb2"));
},
_ => panic!("expected Value"),
}
}
#[test]
fn parsed_show_embeddings_with_limit() {
let router = QueryRouter::new();
for i in 0..10 {
router
.execute_parsed(&format!("EMBED STORE 'key{}' [{}]", i, i as f32))
.unwrap();
}
let result = router.execute_parsed("SHOW EMBEDDINGS LIMIT 5");
assert!(result.is_ok());
}
#[test]
fn parsed_count_embeddings() {
let router = QueryRouter::new();
router.execute_parsed("EMBED STORE 'a' [1.0]").unwrap();
router.execute_parsed("EMBED STORE 'b' [2.0]").unwrap();
router.execute_parsed("EMBED STORE 'c' [3.0]").unwrap();
let result = router.execute_parsed("COUNT EMBEDDINGS");
assert!(result.is_ok());
match result.unwrap() {
QueryResult::Count(n) => {
assert_eq!(n, 3);
},
_ => panic!("expected Count"),
}
}
#[test]
fn test_query_result_to_json() {
let result = QueryResult::Value("test".to_string());
let json = result.to_json();
assert!(json.contains("Value"));
assert!(json.contains("test"));
}
#[test]
fn test_query_result_to_pretty_json() {
let result = QueryResult::Count(42);
let json = result.to_pretty_json();
assert!(json.contains("Count"));
assert!(json.contains("42"));
}
#[test]
fn test_query_result_is_empty() {
assert!(QueryResult::Empty.is_empty());
assert!(!QueryResult::Value("x".to_string()).is_empty());
}
#[test]
fn test_query_result_as_count() {
assert_eq!(QueryResult::Count(10).as_count(), Some(10));
assert_eq!(QueryResult::Empty.as_count(), None);
}
#[test]
fn test_query_result_as_value() {
let result = QueryResult::Value("hello".to_string());
assert_eq!(result.as_value(), Some("hello"));
assert_eq!(QueryResult::Empty.as_value(), None);
}
#[test]
fn test_query_result_as_rows() {
let values = vec![("name".to_string(), Value::String("test".to_string()))];
let rows = vec![Row { id: 1, values }];
let result = QueryResult::Rows(rows);
assert!(result.as_rows().is_some());
assert_eq!(result.as_rows().unwrap().len(), 1);
assert!(QueryResult::Empty.as_rows().is_none());
}
#[test]
fn test_ensure_cache_auto_init() {
let mut router = QueryRouter::new();
assert!(router.cache().is_none());
let cache = router.ensure_cache();
assert_eq!(cache.stats().total_entries(), 0);
let cache2 = router.ensure_cache();
assert_eq!(cache2.stats().total_entries(), 0);
}
#[test]
fn test_ensure_blob_auto_init() {
let mut router = QueryRouter::new();
assert!(router.blob().is_none());
let result = router.ensure_blob();
assert!(result.is_ok());
let result2 = router.ensure_blob();
assert!(result2.is_ok());
}
#[test]
fn test_ensure_vault_no_env_key() {
let mut router = QueryRouter::new();
assert!(router.vault().is_none());
let saved = std::env::var("NEUMANN_VAULT_KEY").ok();
std::env::remove_var("NEUMANN_VAULT_KEY");
let result = router.ensure_vault();
assert!(result.is_err());
if let Err(err) = result {
assert!(err.to_string().contains("not initialized"));
}
if let Some(key) = saved {
std::env::set_var("NEUMANN_VAULT_KEY", key);
}
}
#[test]
fn test_ensure_vault_with_pre_init() {
let mut router = QueryRouter::new();
router
.init_vault(b"32_byte_master_key_for_testing!")
.unwrap();
let result = router.ensure_vault();
assert!(result.is_ok());
}
#[test]
fn test_ensure_cache_idempotent() {
let mut router = QueryRouter::new();
let _ = router.ensure_cache();
let _ = router.ensure_cache();
let _ = router.ensure_cache();
assert!(router.cache().is_some());
}
#[test]
fn test_ensure_blob_idempotent() {
let mut router = QueryRouter::new();
let _ = router.ensure_blob();
let _ = router.ensure_blob();
let _ = router.ensure_blob();
assert!(router.blob().is_some());
}
#[tokio::test]
async fn test_execute_parsed_async_basic() {
let router = QueryRouter::new();
let result = router
.execute_parsed_async("CREATE TABLE async_test (id INT, name VARCHAR(100))")
.await;
assert!(result.is_ok());
let result = router
.execute_parsed_async("INSERT INTO async_test (id, name) VALUES (1, 'test')")
.await;
assert!(result.is_ok());
let result = router
.execute_parsed_async("SELECT * FROM async_test")
.await;
assert!(result.is_ok());
if let QueryResult::Rows(rows) = result.unwrap() {
assert_eq!(rows.len(), 1);
}
}
#[tokio::test]
async fn test_execute_statement_async_delegates() {
let router = QueryRouter::new();
let stmt = parser::parse("NODE CREATE user { name: 'Alice' }").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_embed_batch_parallel() {
let router = QueryRouter::new();
let items: Vec<(String, Vec<f32>)> = (0..10)
.map(|i| (format!("parallel:{}", i), vec![i as f32 / 10.0; 4]))
.collect();
let result = router.embed_batch_parallel(items).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 10);
for i in 0..10 {
let key = format!("parallel:{}", i);
let emb = router.vector().get_embedding(&key);
assert!(emb.is_ok());
}
}
#[tokio::test]
async fn test_find_similar_connected_async() {
let router = QueryRouter::new();
router
.vector()
.set_entity_embedding("query", vec![1.0, 0.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:1", vec![0.9, 0.1, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("user:2", vec![0.8, 0.2, 0.0])
.unwrap();
add_test_edge(router.graph(), "hub", "user:1", "connects");
add_test_edge(router.graph(), "hub", "user:2", "connects");
let result = router.find_similar_connected_async("query", "hub", 5).await;
assert!(result.is_ok());
let items = result.unwrap();
assert!(!items.is_empty());
}
#[tokio::test]
async fn test_find_neighbors_by_similarity_async() {
let router = QueryRouter::new();
add_test_edge(router.graph(), "center", "neighbor:1", "links");
add_test_edge(router.graph(), "center", "neighbor:2", "links");
add_test_edge(router.graph(), "center", "neighbor:3", "links");
router
.vector()
.set_entity_embedding("neighbor:1", vec![1.0, 0.0, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("neighbor:2", vec![0.9, 0.1, 0.0])
.unwrap();
router
.vector()
.set_entity_embedding("neighbor:3", vec![0.0, 1.0, 0.0])
.unwrap();
let query = vec![1.0, 0.0, 0.0];
let result = router
.find_neighbors_by_similarity_async("center", &query, 3)
.await;
assert!(result.is_ok());
let items = result.unwrap();
assert_eq!(items.len(), 3);
assert!(items[0].id.contains("neighbor:1") || items[0].score.unwrap() > 0.9);
}
#[test]
fn test_block_on_helper() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.block_on(async { 42 + 1 });
assert!(result.is_ok());
assert_eq!(result.unwrap(), 43);
}
#[test]
fn test_runtime_accessor() {
let router = QueryRouter::new();
assert!(router.runtime().is_none());
let mut router = QueryRouter::new();
router.init_blob().unwrap();
assert!(router.runtime().is_some());
}
#[tokio::test]
async fn test_execute_parsed_async_with_cache() {
let mut router = QueryRouter::new();
router.init_cache();
router
.execute_parsed_async("CREATE TABLE cached (x INT)")
.await
.unwrap();
router
.execute_parsed_async("INSERT INTO cached (x) VALUES (1)")
.await
.unwrap();
let result1 = router.execute_parsed_async("SELECT * FROM cached").await;
assert!(result1.is_ok());
let result2 = router.execute_parsed_async("SELECT * FROM cached").await;
assert!(result2.is_ok());
}
#[tokio::test]
async fn test_embed_batch_parallel_empty() {
let router = QueryRouter::new();
let result = router.embed_batch_parallel(vec![]).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[tokio::test]
async fn test_execute_parsed_async_error() {
let router = QueryRouter::new();
let result = router.execute_parsed_async("INVALID QUERY XYZ").await;
assert!(result.is_err());
}
#[test]
fn test_exec_blob_async_put_get() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'test.txt' 'hello world'").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
let artifact_id = match result.unwrap() {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt = parser::parse(&format!("BLOB GET '{}'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::Blob(data) = result.unwrap() {
assert_eq!(String::from_utf8(data).unwrap(), "hello world");
}
})
.unwrap();
}
#[test]
fn test_exec_blob_async_info() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'info.txt' 'test data'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt = parser::parse(&format!("BLOB INFO '{}'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::ArtifactInfo(info) = result.unwrap() {
assert_eq!(info.filename, "info.txt");
assert_eq!(info.size, 9); }
})
.unwrap();
}
#[test]
fn test_exec_blob_async_link_unlink() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'linked.txt' 'link test'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt =
parser::parse(&format!("BLOB LINK '{}' TO 'entity:1'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
let stmt = parser::parse(&format!("BLOB LINKS '{}'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::ArtifactList(links) = result.unwrap() {
assert!(links.contains(&"entity:1".to_string()));
}
let stmt = parser::parse(&format!("BLOB UNLINK '{}' FROM 'entity:1'", artifact_id))
.unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
})
.unwrap();
}
#[test]
fn test_exec_blob_async_tag_untag() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'tagged.txt' 'tag test'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt =
parser::parse(&format!("BLOB TAG '{}' 'important'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
let stmt =
parser::parse(&format!("BLOB UNTAG '{}' 'important'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
})
.unwrap();
}
#[test]
fn test_exec_blob_async_verify() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'verify.txt' 'verify test'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt = parser::parse(&format!("BLOB VERIFY '{}'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::Value(v) = result.unwrap() {
assert_eq!(v, "OK");
}
})
.unwrap();
}
#[test]
fn test_exec_blob_async_stats() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB STATS").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::BlobStats(stats) = result.unwrap() {
assert!(stats.dedup_ratio >= 0.0);
}
})
.unwrap();
}
#[test]
fn test_exec_blob_async_gc() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB GC").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
})
.unwrap();
}
#[test]
fn test_exec_blob_async_delete() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'delete.txt' 'delete test'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt = parser::parse(&format!("BLOB DELETE '{}'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
})
.unwrap();
}
#[test]
fn test_exec_blob_async_meta() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'meta.txt' 'meta test'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt = parser::parse(&format!("BLOB META SET '{}' 'key' 'value'", artifact_id))
.unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
let stmt =
parser::parse(&format!("BLOB META GET '{}' 'key'", artifact_id)).unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::Value(v) = result.unwrap() {
assert_eq!(v, "value");
}
})
.unwrap();
}
#[test]
fn test_exec_blob_async_repair() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB REPAIR").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
})
.unwrap();
}
#[test]
fn test_exec_blobs_async_list() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'list1.txt' 'data1'").unwrap();
router.execute_statement_async(&stmt).await.unwrap();
let stmt = parser::parse("BLOB PUT 'list2.txt' 'data2'").unwrap();
router.execute_statement_async(&stmt).await.unwrap();
let stmt = parser::parse("BLOBS").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::ArtifactList(ids) = result.unwrap() {
assert!(ids.len() >= 2);
}
})
.unwrap();
}
#[test]
fn test_exec_blobs_async_for_entity() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'entity.txt' 'entity data'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt =
parser::parse(&format!("BLOB LINK '{}' TO 'myentity'", artifact_id)).unwrap();
router.execute_statement_async(&stmt).await.unwrap();
let stmt = parser::parse("BLOBS FOR 'myentity'").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::ArtifactList(ids) = result.unwrap() {
assert!(ids.contains(&artifact_id));
}
})
.unwrap();
}
#[test]
fn test_exec_blobs_async_by_tag() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB PUT 'bytag.txt' 'tag data'").unwrap();
let result = router.execute_statement_async(&stmt).await.unwrap();
let artifact_id = match result {
QueryResult::Value(id) => id,
_ => panic!("Expected Value result"),
};
let stmt = parser::parse(&format!("BLOB TAG '{}' 'mytag'", artifact_id)).unwrap();
router.execute_statement_async(&stmt).await.unwrap();
let stmt = parser::parse("BLOBS BY TAG 'mytag'").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::ArtifactList(ids) = result.unwrap() {
assert!(ids.contains(&artifact_id));
}
})
.unwrap();
}
#[test]
fn test_exec_blob_async_init_already_initialized() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router
.block_on(async {
let stmt = parser::parse("BLOB INIT").unwrap();
let result = router.execute_statement_async(&stmt).await;
assert!(result.is_ok());
if let QueryResult::Value(v) = result.unwrap() {
assert!(v.contains("already initialized"));
}
})
.unwrap();
}
#[test]
fn test_exec_blob_async_not_initialized() {
let router = QueryRouter::new();
let stmt = parser::parse("BLOB STATS").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
}
#[test]
fn test_init_checkpoint_requires_dir() {
let mut router = QueryRouter::new();
let result = router.init_checkpoint();
assert!(result.is_err());
if let Err(RouterError::CheckpointError(msg)) = result {
assert!(msg.contains("Checkpoint directory must be set"));
}
}
#[test]
fn test_init_checkpoint_with_dir() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let result = router.init_checkpoint();
assert!(result.is_ok());
}
#[test]
fn test_init_checkpoint_with_config() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let config = CheckpointConfig::default().with_max_checkpoints(5);
let result = router.init_checkpoint_with_config(config);
assert!(result.is_ok());
}
#[test]
fn test_ensure_checkpoint_auto_init() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let result = router.ensure_checkpoint();
assert!(result.is_ok());
}
#[test]
fn test_ensure_checkpoint_already_initialized() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let result = router.ensure_checkpoint();
assert!(result.is_ok());
}
#[test]
fn test_exec_checkpoint_not_initialized() {
let router = QueryRouter::new();
let stmt = parser::parse("CHECKPOINT").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
if let Err(RouterError::CheckpointError(msg)) = result {
assert!(msg.contains("not initialized"));
}
}
#[test]
fn test_exec_checkpoint_create() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
if let QueryResult::Value(v) = result.unwrap() {
assert!(v.contains("Checkpoint created"));
}
}
#[test]
fn test_exec_checkpoint_with_name() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT 'my-checkpoint'").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
if let QueryResult::Value(v) = result.unwrap() {
assert!(v.contains("Checkpoint created"));
}
}
#[test]
fn test_exec_checkpoints_list() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT 'test-cp'").unwrap();
router.execute_statement(&stmt).unwrap();
let stmt = parser::parse("CHECKPOINTS").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
if let QueryResult::CheckpointList(list) = result.unwrap() {
assert!(!list.is_empty());
assert_eq!(list[0].name, "test-cp");
}
}
#[test]
fn test_exec_checkpoints_with_limit() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
for i in 0..5 {
let stmt = parser::parse(&format!("CHECKPOINT 'cp-{}'", i)).unwrap();
router.execute_statement(&stmt).unwrap();
}
let stmt = parser::parse("CHECKPOINTS LIMIT 3").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
if let QueryResult::CheckpointList(list) = result.unwrap() {
assert_eq!(list.len(), 3);
}
}
#[test]
fn test_exec_checkpoints_not_initialized() {
let router = QueryRouter::new();
let stmt = parser::parse("CHECKPOINTS").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
}
#[test]
fn test_exec_rollback_not_initialized() {
let router = QueryRouter::new();
let stmt = parser::parse("ROLLBACK TO 'some-id'").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
if let Err(RouterError::CheckpointError(msg)) = result {
assert!(msg.contains("not initialized"));
}
}
#[test]
fn test_exec_rollback_success() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
router.execute("EMBED testkey [1.0, 2.0, 3.0]").unwrap();
let cp_stmt = parser::parse("CHECKPOINT 'before-delete'").unwrap();
router.execute_statement(&cp_stmt).unwrap();
router.execute_parsed("EMBED DELETE 'testkey'").unwrap();
assert!(!router.vector().exists("testkey"));
let rb_stmt = parser::parse("ROLLBACK TO 'before-delete'").unwrap();
let result = router.execute_statement(&rb_stmt);
assert!(result.is_ok());
if let QueryResult::Value(v) = result.unwrap() {
assert!(v.contains("Rolled back"));
}
assert!(router.vector().exists("testkey"));
}
#[test]
fn test_exec_rollback_not_found() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("ROLLBACK TO 'nonexistent'").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
}
#[test]
fn test_checkpoint_info_is_auto() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT 'manual'").unwrap();
router.execute_statement(&stmt).unwrap();
let stmt = parser::parse("CHECKPOINTS").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::CheckpointList(list) = result {
assert!(!list[0].is_auto);
}
}
#[test]
fn test_checkpoint_error_display() {
let e = RouterError::CheckpointError("test error".into());
assert!(e.to_string().contains("Checkpoint error"));
assert!(e.to_string().contains("test error"));
}
#[test]
fn test_exec_checkpoint_sync_success() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT 'sync-test'").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
if let QueryResult::Value(v) = result.unwrap() {
assert!(v.contains("Checkpoint created"));
}
}
#[test]
fn test_exec_checkpoints_sync_success() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT 'for-list'").unwrap();
router.execute_statement(&stmt).unwrap();
let stmt = parser::parse("CHECKPOINTS").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
if let QueryResult::CheckpointList(list) = result.unwrap() {
assert!(!list.is_empty());
}
}
#[test]
fn test_exec_rollback_sync_success() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
router.execute("EMBED synckey [1.0, 2.0]").unwrap();
let stmt = parser::parse("CHECKPOINT 'sync-rollback'").unwrap();
router.execute_statement(&stmt).unwrap();
router.execute_parsed("EMBED DELETE 'synckey'").unwrap();
assert!(!router.vector().exists("synckey"));
let stmt = parser::parse("ROLLBACK TO 'sync-rollback'").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
assert!(router.vector().exists("synckey"));
}
#[test]
fn test_checkpoint_with_limit() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINTS LIMIT 5").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
}
#[test]
fn test_checkpoint_list_empty() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINTS").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
if let QueryResult::CheckpointList(list) = result.unwrap() {
assert!(list.is_empty());
}
}
#[test]
fn test_checkpoint_with_double_quoted_name() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT \"double-quoted\"").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
}
#[test]
fn test_rollback_sync_by_id() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT 'rollback-by-id'").unwrap();
router.execute_statement(&stmt).unwrap();
let stmt = parser::parse("CHECKPOINTS").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let checkpoint_id = if let QueryResult::CheckpointList(list) = result {
list[0].id.clone()
} else {
panic!("Expected CheckpointList");
};
let stmt = parser::parse(&format!("ROLLBACK TO '{}'", checkpoint_id)).unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
}
#[test]
fn test_multiple_checkpoints_ordering() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
router
.execute_statement(&parser::parse("CHECKPOINT 'first'").unwrap())
.unwrap();
router
.execute_statement(&parser::parse("CHECKPOINT 'second'").unwrap())
.unwrap();
router
.execute_statement(&parser::parse("CHECKPOINT 'third'").unwrap())
.unwrap();
let result = router
.execute_statement(&parser::parse("CHECKPOINTS").unwrap())
.unwrap();
if let QueryResult::CheckpointList(list) = result {
assert_eq!(list.len(), 3);
}
}
#[test]
fn test_checkpoint_via_execute_parsed() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let result = router.execute_parsed("CHECKPOINT 'parsed-test'");
assert!(result.is_ok());
}
#[test]
fn test_checkpoints_via_execute_parsed() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
router.execute_parsed("CHECKPOINT 'test1'").unwrap();
router.execute_parsed("CHECKPOINT 'test2'").unwrap();
let result = router.execute_parsed("CHECKPOINTS");
assert!(result.is_ok());
}
#[test]
fn test_rollback_via_execute_parsed() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
router
.execute_parsed("CHECKPOINT 'rollback-parsed'")
.unwrap();
let result = router.execute_parsed("ROLLBACK TO 'rollback-parsed'");
assert!(result.is_ok());
}
#[test]
fn test_checkpoint_default_name() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
let stmt = parser::parse("CHECKPOINT").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_ok());
let list_result = router
.execute_statement(&parser::parse("CHECKPOINTS").unwrap())
.unwrap();
if let QueryResult::CheckpointList(list) = list_result {
assert_eq!(list.len(), 1);
assert!(list[0].name.starts_with("checkpoint-"));
}
}
#[test]
fn test_chain_not_initialized() {
let router = QueryRouter::new();
let stmt = parser::parse("CHAIN HEIGHT").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
if let Err(RouterError::ChainError(msg)) = result {
assert!(msg.contains("not initialized"));
}
}
#[test]
fn test_chain_height() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN HEIGHT").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Height(h)) = result {
assert_eq!(h, 0);
} else {
panic!("expected CHAIN HEIGHT result");
}
}
#[test]
fn test_chain_tip() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN TIP").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Tip { height, .. }) = result {
assert_eq!(height, 0);
} else {
panic!("expected CHAIN TIP result");
}
}
#[test]
fn test_chain_verify() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN VERIFY").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Verified { ok, errors }) = result {
assert!(ok);
assert!(errors.is_empty());
} else {
panic!("expected CHAIN VERIFY result");
}
}
#[test]
fn test_chain_block_not_found() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN BLOCK 999").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
}
#[test]
fn test_chain_block_genesis() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN BLOCK 0").unwrap();
let result = router.execute_statement(&stmt);
match result {
Ok(QueryResult::Chain(ChainResult::Block(info))) => {
assert_eq!(info.height, 0);
assert!(!info.hash.is_empty());
},
_ => panic!("Expected CHAIN BLOCK result, got {:?}", result),
}
}
#[test]
fn test_chain_history() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN HISTORY 'test_key'").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::History(entries)) = result {
assert!(entries.is_empty());
} else {
panic!("expected CHAIN HISTORY result");
}
}
#[test]
fn test_chain_drift() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN DRIFT FROM 0 TO 100").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Drift(drift)) = result {
assert_eq!(drift.from_height, 0);
assert_eq!(drift.to_height, 100);
} else {
panic!("expected CHAIN DRIFT result");
}
}
#[test]
fn test_chain_begin() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("BEGIN CHAIN TRANSACTION").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::TransactionBegun { tx_id }) = result {
assert!(!tx_id.is_empty());
} else {
panic!("expected CHAIN BEGIN result");
}
}
#[test]
fn test_show_codebook_global() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("SHOW CODEBOOK GLOBAL").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Codebook(info)) = result {
assert_eq!(info.scope, "global");
} else {
panic!("expected SHOW CODEBOOK GLOBAL result");
}
}
#[test]
fn test_show_codebook_local() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("SHOW CODEBOOK LOCAL 'users'").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Codebook(info)) = result {
assert_eq!(info.scope, "local");
assert_eq!(info.domain, Some("users".to_string()));
} else {
panic!("expected SHOW CODEBOOK LOCAL result");
}
}
#[test]
fn test_analyze_codebook_transitions() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("ANALYZE CODEBOOK TRANSITIONS").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::TransitionAnalysis(analysis)) = result {
assert_eq!(analysis.total_transitions, 0);
} else {
panic!("expected ANALYZE CODEBOOK TRANSITIONS result");
}
}
fn setup_join_tables(router: &QueryRouter) {
router
.execute_parsed("CREATE TABLE users (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("CREATE TABLE orders (id INT, user_id INT, amount INT)")
.unwrap();
router
.execute_parsed("INSERT INTO users (id, name) VALUES (1, 'Alice')")
.unwrap();
router
.execute_parsed("INSERT INTO users (id, name) VALUES (2, 'Bob')")
.unwrap();
router
.execute_parsed("INSERT INTO users (id, name) VALUES (3, 'Charlie')")
.unwrap();
router
.execute_parsed("INSERT INTO orders (id, user_id, amount) VALUES (101, 1, 100)")
.unwrap();
router
.execute_parsed("INSERT INTO orders (id, user_id, amount) VALUES (102, 1, 200)")
.unwrap();
router
.execute_parsed("INSERT INTO orders (id, user_id, amount) VALUES (103, 2, 150)")
.unwrap();
router
.execute_parsed("INSERT INTO orders (id, user_id, amount) VALUES (104, 99, 50)")
.unwrap();
}
#[test]
fn test_inner_join_via_router() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt =
parser::parse("SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id")
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
let alice_orders: Vec<_> = rows
.iter()
.filter(|r| {
r.values
.iter()
.any(|(k, v)| k == "users.name" && v == &Value::String("Alice".to_string()))
})
.collect();
assert_eq!(alice_orders.len(), 2);
}
#[test]
fn test_left_join_via_router() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt =
parser::parse("SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id")
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 4);
let charlie_row = rows
.iter()
.find(|r| {
r.values
.iter()
.any(|(k, v)| k == "users.name" && v == &Value::String("Charlie".to_string()))
})
.expect("Charlie should be in result");
let has_orders_id = charlie_row.values.iter().any(|(k, _)| k == "orders._id");
assert!(!has_orders_id, "Charlie should not have orders._id");
}
#[test]
fn test_right_join_via_router() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt =
parser::parse("SELECT * FROM users RIGHT JOIN orders ON users.id = orders.user_id")
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 4);
let orphan_order = rows
.iter()
.find(|r| {
r.values
.iter()
.any(|(k, v)| k == "orders.id" && v == &Value::Int(104))
})
.expect("Order 104 should be in result");
let has_user_id = orphan_order.values.iter().any(|(k, _)| k == "users._id");
assert!(!has_user_id, "Orphan order should not have users._id");
}
#[test]
fn test_full_join_via_router() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt =
parser::parse("SELECT * FROM users FULL JOIN orders ON users.id = orders.user_id")
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 5);
}
#[test]
fn test_cross_join_via_router() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse("SELECT * FROM users CROSS JOIN orders").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 12);
}
#[test]
fn test_natural_join_via_router() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE departments (dept_id INT, name TEXT)")
.unwrap();
router
.execute_parsed("CREATE TABLE employees (emp_id INT, dept_id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO departments (dept_id, name) VALUES (1, 'Engineering')")
.unwrap();
router
.execute_parsed("INSERT INTO departments (dept_id, name) VALUES (2, 'Sales')")
.unwrap();
router
.execute_parsed(
"INSERT INTO employees (emp_id, dept_id, name) VALUES (100, 1, 'Alice')",
)
.unwrap();
router
.execute_parsed("INSERT INTO employees (emp_id, dept_id, name) VALUES (101, 1, 'Bob')")
.unwrap();
router
.execute_parsed(
"INSERT INTO employees (emp_id, dept_id, name) VALUES (102, 2, 'Charlie')",
)
.unwrap();
let stmt = parser::parse("SELECT * FROM departments NATURAL JOIN employees").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 0);
}
#[test]
fn test_join_with_where_clause() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id WHERE orders.amount > 100"
).unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_join_with_limit() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id LIMIT 2",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_join_using_clause() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE products (product_id INT, name TEXT)")
.unwrap();
router
.execute_parsed("CREATE TABLE sales (sale_id INT, product_id INT, qty INT)")
.unwrap();
router
.execute_parsed("INSERT INTO products (product_id, name) VALUES (1, 'Widget')")
.unwrap();
router
.execute_parsed("INSERT INTO products (product_id, name) VALUES (2, 'Gadget')")
.unwrap();
router
.execute_parsed("INSERT INTO sales (sale_id, product_id, qty) VALUES (100, 1, 10)")
.unwrap();
router
.execute_parsed("INSERT INTO sales (sale_id, product_id, qty) VALUES (101, 1, 5)")
.unwrap();
let stmt =
parser::parse("SELECT * FROM products INNER JOIN sales USING (product_id)").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_order_by_asc() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT, name TEXT, price INT)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name, price) VALUES (1, 'Apple', 100)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name, price) VALUES (2, 'Banana', 50)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name, price) VALUES (3, 'Cherry', 200)")
.unwrap();
let stmt = parser::parse("SELECT * FROM items ORDER BY price ASC").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
assert_eq!(
rows[0].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Banana".to_string())
);
assert_eq!(
rows[1].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Apple".to_string())
);
assert_eq!(
rows[2].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Cherry".to_string())
);
}
#[test]
fn test_order_by_desc() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT, name TEXT, price INT)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name, price) VALUES (1, 'Apple', 100)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name, price) VALUES (2, 'Banana', 50)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name, price) VALUES (3, 'Cherry', 200)")
.unwrap();
let stmt = parser::parse("SELECT * FROM items ORDER BY price DESC").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
assert_eq!(
rows[0].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Cherry".to_string())
);
assert_eq!(
rows[1].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Apple".to_string())
);
assert_eq!(
rows[2].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Banana".to_string())
);
}
#[test]
fn test_order_by_string() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (1, 'Cherry')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (2, 'Apple')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (3, 'Banana')")
.unwrap();
let stmt = parser::parse("SELECT * FROM items ORDER BY name").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
assert_eq!(
rows[0].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Apple".to_string())
);
assert_eq!(
rows[1].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Banana".to_string())
);
assert_eq!(
rows[2].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("Cherry".to_string())
);
}
#[test]
fn test_order_by_multiple_columns() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT, category TEXT, price INT)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, category, price) VALUES (1, 'Fruit', 100)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, category, price) VALUES (2, 'Fruit', 50)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, category, price) VALUES (3, 'Veggie', 75)")
.unwrap();
let stmt = parser::parse("SELECT * FROM items ORDER BY category ASC, price DESC").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
assert_eq!(
rows[0].values.iter().find(|(k, _)| k == "id").unwrap().1,
Value::Int(1)
); assert_eq!(
rows[1].values.iter().find(|(k, _)| k == "id").unwrap().1,
Value::Int(2)
); assert_eq!(
rows[2].values.iter().find(|(k, _)| k == "id").unwrap().1,
Value::Int(3)
); }
#[test]
fn test_offset() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (1, 'A')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (2, 'B')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (3, 'C')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (4, 'D')")
.unwrap();
let stmt = parser::parse("SELECT * FROM items ORDER BY id OFFSET 2").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
assert_eq!(
rows[0].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("C".to_string())
);
assert_eq!(
rows[1].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("D".to_string())
);
}
#[test]
fn test_order_by_with_limit_and_offset() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (1, 'A')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (2, 'B')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (3, 'C')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (4, 'D')")
.unwrap();
router
.execute_parsed("INSERT INTO items (id, name) VALUES (5, 'E')")
.unwrap();
let stmt = parser::parse("SELECT * FROM items ORDER BY id LIMIT 2 OFFSET 1").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
assert_eq!(
rows[0].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("B".to_string())
);
assert_eq!(
rows[1].values.iter().find(|(k, _)| k == "name").unwrap().1,
Value::String("C".to_string())
);
}
#[test]
fn test_offset_beyond_rows() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE items (id INT)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id) VALUES (1)")
.unwrap();
router
.execute_parsed("INSERT INTO items (id) VALUES (2)")
.unwrap();
let stmt = parser::parse("SELECT * FROM items OFFSET 10").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 0);
}
#[test]
fn test_order_by_with_join() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id ORDER BY orders.amount DESC"
).unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
let amounts: Vec<_> = rows
.iter()
.map(|r| {
r.values
.iter()
.find(|(k, _)| k == "orders.amount")
.unwrap()
.1
.clone()
})
.collect();
assert_eq!(
amounts,
vec![Value::Int(200), Value::Int(150), Value::Int(100)]
);
}
fn setup_aggregate_table(router: &QueryRouter) {
router
.execute_parsed("CREATE TABLE sales (id INT, product TEXT, amount INT, price FLOAT)")
.unwrap();
router
.execute_parsed(
"INSERT INTO sales (id, product, amount, price) VALUES (1, 'Apple', 10, 1.50)",
)
.unwrap();
router
.execute_parsed(
"INSERT INTO sales (id, product, amount, price) VALUES (2, 'Banana', 20, 0.75)",
)
.unwrap();
router
.execute_parsed(
"INSERT INTO sales (id, product, amount, price) VALUES (3, 'Cherry', 15, 2.00)",
)
.unwrap();
router
.execute_parsed(
"INSERT INTO sales (id, product, amount, price) VALUES (4, 'Apple', 5, 1.50)",
)
.unwrap();
}
#[test]
fn test_count_star() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT COUNT(*) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let count = rows[0]
.values
.iter()
.find(|(k, _)| k == "COUNT(*)")
.unwrap()
.1
.clone();
assert_eq!(count, Value::Int(4));
}
#[test]
fn test_count_column() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT COUNT(product) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let count = rows[0]
.values
.iter()
.find(|(k, _)| k == "COUNT(product)")
.unwrap()
.1
.clone();
assert_eq!(count, Value::Int(4));
}
#[test]
fn test_sum() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT SUM(amount) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let sum = rows[0]
.values
.iter()
.find(|(k, _)| k == "SUM(amount)")
.unwrap()
.1
.clone();
assert_eq!(sum, Value::Float(50.0)); }
#[test]
fn test_avg() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT AVG(amount) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let avg = rows[0]
.values
.iter()
.find(|(k, _)| k == "AVG(amount)")
.unwrap()
.1
.clone();
assert_eq!(avg, Value::Float(12.5)); }
#[test]
fn test_min() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT MIN(amount) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let min = rows[0]
.values
.iter()
.find(|(k, _)| k == "MIN(amount)")
.unwrap()
.1
.clone();
assert_eq!(min, Value::Int(5));
}
#[test]
fn test_max() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT MAX(amount) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let max = rows[0]
.values
.iter()
.find(|(k, _)| k == "MAX(amount)")
.unwrap()
.1
.clone();
assert_eq!(max, Value::Int(20));
}
#[test]
fn test_multiple_aggregates() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT COUNT(*), SUM(amount), AVG(price) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let count = rows[0]
.values
.iter()
.find(|(k, _)| k == "COUNT(*)")
.unwrap()
.1
.clone();
let sum = rows[0]
.values
.iter()
.find(|(k, _)| k == "SUM(amount)")
.unwrap()
.1
.clone();
let avg = rows[0]
.values
.iter()
.find(|(k, _)| k == "AVG(price)")
.unwrap()
.1
.clone();
assert_eq!(count, Value::Int(4));
assert_eq!(sum, Value::Float(50.0));
if let Value::Float(f) = avg {
assert!((f - 1.4375).abs() < 0.0001);
} else {
panic!("expected Float");
}
}
#[test]
fn test_aggregate_with_where() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT COUNT(*), SUM(amount) FROM sales WHERE product = 'Apple'")
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let count = rows[0]
.values
.iter()
.find(|(k, _)| k == "COUNT(*)")
.unwrap()
.1
.clone();
let sum = rows[0]
.values
.iter()
.find(|(k, _)| k == "SUM(amount)")
.unwrap()
.1
.clone();
assert_eq!(count, Value::Int(2)); assert_eq!(sum, Value::Float(15.0)); }
#[test]
fn test_aggregate_with_alias() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT COUNT(*) AS total_count FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let count = rows[0]
.values
.iter()
.find(|(k, _)| k == "total_count")
.unwrap()
.1
.clone();
assert_eq!(count, Value::Int(4));
}
#[test]
fn test_min_max_string() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT MIN(product), MAX(product) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let min = rows[0]
.values
.iter()
.find(|(k, _)| k == "MIN(product)")
.unwrap()
.1
.clone();
let max = rows[0]
.values
.iter()
.find(|(k, _)| k == "MAX(product)")
.unwrap()
.1
.clone();
assert_eq!(min, Value::String("Apple".to_string()));
assert_eq!(max, Value::String("Cherry".to_string()));
}
#[test]
fn test_group_by_single_column() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT product, COUNT(*) FROM sales GROUP BY product").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
let get_count = |product: &str| -> i64 {
rows.iter()
.find(|r| {
r.values
.iter()
.any(|(k, v)| k == "product" && *v == Value::String(product.to_string()))
})
.and_then(|r| r.values.iter().find(|(k, _)| k == "COUNT(*)"))
.map(|(_, v)| if let Value::Int(i) = v { *i } else { 0 })
.unwrap_or(0)
};
assert_eq!(get_count("Apple"), 2);
assert_eq!(get_count("Banana"), 1);
assert_eq!(get_count("Cherry"), 1);
}
#[test]
fn test_group_by_with_sum() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt =
parser::parse("SELECT product, SUM(amount) FROM sales GROUP BY product").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
let get_sum = |product: &str| -> f64 {
rows.iter()
.find(|r| {
r.values
.iter()
.any(|(k, v)| k == "product" && *v == Value::String(product.to_string()))
})
.and_then(|r| r.values.iter().find(|(k, _)| k == "SUM(amount)"))
.map(|(_, v)| if let Value::Float(f) = v { *f } else { 0.0 })
.unwrap_or(0.0)
};
assert_eq!(get_sum("Apple"), 15.0); assert_eq!(get_sum("Banana"), 20.0); assert_eq!(get_sum("Cherry"), 15.0); }
#[test]
fn test_group_by_with_avg() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt =
parser::parse("SELECT product, AVG(amount) FROM sales GROUP BY product").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
let get_avg = |product: &str| -> f64 {
rows.iter()
.find(|r| {
r.values
.iter()
.any(|(k, v)| k == "product" && *v == Value::String(product.to_string()))
})
.and_then(|r| r.values.iter().find(|(k, _)| k == "AVG(amount)"))
.map(|(_, v)| if let Value::Float(f) = v { *f } else { 0.0 })
.unwrap_or(0.0)
};
assert_eq!(get_avg("Apple"), 7.5); assert_eq!(get_avg("Banana"), 20.0); assert_eq!(get_avg("Cherry"), 15.0); }
#[test]
fn test_group_by_with_having() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse(
"SELECT product, COUNT(*) FROM sales GROUP BY product HAVING COUNT(*) > 1",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1); let product = rows[0]
.values
.iter()
.find(|(k, _)| k == "product")
.unwrap()
.1
.clone();
assert_eq!(product, Value::String("Apple".to_string()));
}
#[test]
fn test_group_by_with_having_sum() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse(
"SELECT product, SUM(amount) FROM sales GROUP BY product HAVING SUM(amount) > 15",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let products: Vec<String> = rows
.iter()
.filter_map(|r| r.values.iter().find(|(k, _)| k == "product"))
.filter_map(|(_, v)| {
if let Value::String(s) = v {
Some(s.clone())
} else {
None
}
})
.collect();
assert!(products.contains(&"Banana".to_string()));
}
#[test]
fn test_group_by_with_where_and_having() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT product, COUNT(*), SUM(amount) FROM sales WHERE amount > 5 GROUP BY product HAVING COUNT(*) >= 1").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
}
#[test]
fn test_group_by_multiple_aggregates() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT product, COUNT(*), SUM(amount), AVG(amount), MIN(amount), MAX(amount) FROM sales GROUP BY product").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3);
let apple_row = rows
.iter()
.find(|r| {
r.values
.iter()
.any(|(k, v)| k == "product" && *v == Value::String("Apple".to_string()))
})
.expect("Apple row not found");
let count = apple_row
.values
.iter()
.find(|(k, _)| k == "COUNT(*)")
.unwrap()
.1
.clone();
let sum = apple_row
.values
.iter()
.find(|(k, _)| k == "SUM(amount)")
.unwrap()
.1
.clone();
let avg = apple_row
.values
.iter()
.find(|(k, _)| k == "AVG(amount)")
.unwrap()
.1
.clone();
let min = apple_row
.values
.iter()
.find(|(k, _)| k == "MIN(amount)")
.unwrap()
.1
.clone();
let max = apple_row
.values
.iter()
.find(|(k, _)| k == "MAX(amount)")
.unwrap()
.1
.clone();
assert_eq!(count, Value::Int(2));
assert_eq!(sum, Value::Float(15.0));
assert_eq!(avg, Value::Float(7.5));
assert_eq!(min, Value::Int(5));
assert_eq!(max, Value::Int(10));
}
#[test]
fn test_having_without_matching_groups() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse(
"SELECT product, COUNT(*) FROM sales GROUP BY product HAVING COUNT(*) > 10",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 0); }
#[test]
fn test_delete_without_checkpoint_manager() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE temp (id int, name string)")
.unwrap();
router
.execute("INSERT INTO temp (id, name) VALUES (1, 'test')")
.unwrap();
let result = router.execute("DELETE FROM temp WHERE id = 1");
assert!(result.is_ok(), "Delete failed: {result:?}");
if let Ok(QueryResult::Count(n)) = result {
assert_eq!(n, 1);
}
}
#[test]
fn test_delete_creates_auto_checkpoint() {
use tensor_checkpoint::{AutoConfirm, CheckpointConfig};
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let config = CheckpointConfig::default()
.with_auto_checkpoint(true)
.with_interactive_confirm(true);
router.init_checkpoint_with_config(config).unwrap();
router
.set_confirmation_handler(Arc::new(AutoConfirm))
.unwrap();
router
.execute("CREATE TABLE users (id int, name string)")
.unwrap();
router
.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')")
.unwrap();
router
.execute("INSERT INTO users (id, name) VALUES (2, 'Bob')")
.unwrap();
let result = router.execute("DELETE FROM users WHERE id = 1");
assert!(result.is_ok());
let checkpoints = router.execute_parsed("CHECKPOINTS").unwrap();
let list = unwrap_qr_checkpointlist(checkpoints);
assert!(!list.is_empty());
assert!(list[0].name.contains("auto-before-delete"));
}
#[test]
fn test_delete_cancelled_preserves_data() {
use tensor_checkpoint::{AutoReject, CheckpointConfig};
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let config = CheckpointConfig::default()
.with_auto_checkpoint(true)
.with_interactive_confirm(true);
router.init_checkpoint_with_config(config).unwrap();
router
.set_confirmation_handler(Arc::new(AutoReject))
.unwrap();
router
.execute("CREATE TABLE users (id int, name string)")
.unwrap();
router
.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')")
.unwrap();
let result = router.execute("DELETE FROM users WHERE id = 1");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("cancelled"));
let select = router.execute("SELECT * FROM users WHERE id = 1").unwrap();
let rows = unwrap_qr_rows(select);
assert_eq!(rows.len(), 1);
}
#[test]
fn test_delete_with_auto_checkpoint_disabled() {
use tensor_checkpoint::{AutoReject, CheckpointConfig};
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let config = CheckpointConfig::default()
.with_auto_checkpoint(false)
.with_interactive_confirm(false);
router.init_checkpoint_with_config(config).unwrap();
router
.set_confirmation_handler(Arc::new(AutoReject))
.unwrap();
router.execute("CREATE TABLE temp (id int)").unwrap();
router.execute("INSERT INTO temp (id) VALUES (1)").unwrap();
let result = router.execute("DELETE FROM temp WHERE id = 1");
assert!(result.is_ok());
}
#[test]
fn test_drop_table_creates_checkpoint() {
use tensor_checkpoint::{AutoConfirm, CheckpointConfig};
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let config = CheckpointConfig::default()
.with_auto_checkpoint(true)
.with_interactive_confirm(true);
router.init_checkpoint_with_config(config).unwrap();
router
.set_confirmation_handler(Arc::new(AutoConfirm))
.unwrap();
router.execute("CREATE TABLE to_drop (id int)").unwrap();
router
.execute("INSERT INTO to_drop (id) VALUES (1)")
.unwrap();
let result = router.execute("DROP TABLE to_drop");
assert!(result.is_ok());
let checkpoints = router.execute_parsed("CHECKPOINTS").unwrap();
let list = unwrap_qr_checkpointlist(checkpoints);
assert!(!list.is_empty());
assert!(list[0].name.contains("auto-before-drop-table"));
}
#[test]
fn test_node_delete_creates_checkpoint() {
use tensor_checkpoint::{AutoConfirm, CheckpointConfig};
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
let config = CheckpointConfig::default()
.with_auto_checkpoint(true)
.with_interactive_confirm(true);
router.init_checkpoint_with_config(config).unwrap();
router
.set_confirmation_handler(Arc::new(AutoConfirm))
.unwrap();
let node_id = match router
.execute("NODE CREATE Person { name: 'Alice' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids result"),
};
let result = router.execute(&format!("NODE DELETE {node_id}"));
assert!(result.is_ok());
let checkpoints = router.execute_parsed("CHECKPOINTS").unwrap();
let list = unwrap_qr_checkpointlist(checkpoints);
assert!(!list.is_empty());
assert!(list[0].name.contains("auto-before-node-delete"));
}
#[test]
fn test_collect_delete_sample() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE users (id int, name string)")
.unwrap();
router
.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')")
.unwrap();
router
.execute("INSERT INTO users (id, name) VALUES (2, 'Bob')")
.unwrap();
router
.execute("INSERT INTO users (id, name) VALUES (3, 'Charlie')")
.unwrap();
let condition = relational_engine::Condition::True;
let (count, samples) = router.collect_delete_sample("users", &condition, 5);
assert_eq!(count, 3);
assert!(!samples.is_empty());
assert!(samples.len() <= 5);
}
#[test]
fn test_collect_table_sample() {
let router = QueryRouter::new();
router.execute("CREATE TABLE items (id int)").unwrap();
router.execute("INSERT INTO items (id) VALUES (1)").unwrap();
router.execute("INSERT INTO items (id) VALUES (2)").unwrap();
let (count, samples) = router.collect_table_sample("items", 3);
assert_eq!(count, 2);
assert!(!samples.is_empty());
}
#[test]
fn test_collect_node_info() {
let router = QueryRouter::new();
let alice_id = match router
.execute("NODE CREATE Person { name: 'Alice' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let bob_id = match router
.execute("NODE CREATE Person { name: 'Bob' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {alice_id} -> {bob_id} : KNOWS"))
.unwrap();
let (edge_count, info) = router.collect_node_info(alice_id);
assert_eq!(edge_count, 1);
assert!(!info.is_empty());
}
#[test]
fn test_graph_pagerank() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE Page { url: 'a' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE Page { url: 'b' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let c = match router.execute("NODE CREATE Page { url: 'c' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : linked", a, b))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {} : linked", b, c))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {} : linked", c, a))
.unwrap();
let result = router.execute("GRAPH PAGERANK").unwrap();
match result {
QueryResult::PageRank(pr) => {
assert_eq!(pr.items.len(), 3);
for item in &pr.items {
assert!(item.score > 0.0);
}
},
_ => panic!("Expected PageRank result"),
}
}
#[test]
fn test_graph_pagerank_with_options() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE Page").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE Page").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : linked", a, b))
.unwrap();
let result = router
.execute("GRAPH PAGERANK DAMPING 0.85 ITERATIONS 50")
.unwrap();
assert!(matches!(result, QueryResult::PageRank(_)));
}
#[test]
fn test_graph_betweenness_centrality() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let c = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : conn", a, b))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {} : conn", b, c))
.unwrap();
let result = router.execute("GRAPH BETWEENNESS CENTRALITY").unwrap();
match result {
QueryResult::Centrality(scores) => {
assert_eq!(scores.items.len(), 3);
},
_ => panic!("Expected Centrality result"),
}
}
#[test]
fn test_graph_closeness_centrality() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : conn", a, b))
.unwrap();
let result = router.execute("GRAPH CLOSENESS CENTRALITY").unwrap();
assert!(matches!(result, QueryResult::Centrality(_)));
}
#[test]
fn test_graph_eigenvector_centrality() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let c = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : conn", a, b))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {} : conn", b, c))
.unwrap();
let result = router.execute("GRAPH EIGENVECTOR CENTRALITY").unwrap();
match result {
QueryResult::Centrality(c) => {
assert_eq!(c.centrality_type, CentralityType::Eigenvector);
assert!(!c.items.is_empty());
},
_ => panic!("Expected Centrality result"),
}
}
#[test]
fn test_graph_eigenvector_centrality_with_options() {
let router = QueryRouter::new();
for i in 0..5 {
router
.execute(&format!("NODE CREATE nd {{ id: {} }}", i))
.unwrap();
}
for i in 0..4 {
let from = i + 1;
let to = i + 2;
router
.execute(&format!("EDGE CREATE {} -> {} : conn", from, to))
.unwrap();
}
let result = router
.execute("GRAPH EIGENVECTOR CENTRALITY ITERATIONS 50 TOLERANCE 0.001")
.unwrap();
assert!(matches!(result, QueryResult::Centrality(_)));
}
#[test]
fn test_graph_louvain_communities() {
let router = QueryRouter::new();
let a1 = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let a2 = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : rel", a1, a2))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {} : rel", a2, a1))
.unwrap();
let result = router.execute("GRAPH LOUVAIN COMMUNITIES").unwrap();
assert!(matches!(result, QueryResult::Communities(_)));
}
#[test]
fn test_graph_label_propagation() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {} : rel", a, b))
.unwrap();
let result = router.execute("GRAPH LABEL PROPAGATION").unwrap();
assert!(matches!(result, QueryResult::Communities(_)));
}
#[test]
fn test_graph_index_create_node_property() {
let router = QueryRouter::new();
router
.execute("NODE CREATE Person { name: 'Test' }")
.unwrap();
let result = router
.execute("GRAPH INDEX CREATE ON NODE PROPERTY name")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_index_show() {
let router = QueryRouter::new();
let result = router.execute("GRAPH INDEX SHOW ON NODE").unwrap();
assert!(matches!(result, QueryResult::GraphIndexes(_)));
}
#[test]
fn test_constraint_create_list_get_drop() {
let router = QueryRouter::new();
let result = router
.execute("CONSTRAINT CREATE email_unique ON NODE PROPERTY email UNIQUE")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
let result = router.execute("CONSTRAINT LIST").unwrap();
match result {
QueryResult::Constraints(constraints) => {
assert!(!constraints.is_empty());
},
_ => panic!("Expected Constraints result"),
}
let result = router.execute("CONSTRAINT GET email_unique").unwrap();
match result {
QueryResult::Constraints(c) => assert!(!c.is_empty()),
_ => panic!("Expected Constraints result"),
}
let result = router.execute("CONSTRAINT DROP email_unique").unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_batch_create_nodes() {
let router = QueryRouter::new();
let result = router
.execute("BATCH CREATE NODES [{labels: [Person], name: 'Alice'}, {labels: [Person], name: 'Bob'}]")
.unwrap();
match result {
QueryResult::BatchResult(batch) => {
assert_eq!(batch.affected_count, 2);
assert!(batch.created_ids.is_some());
},
_ => panic!("Expected BatchResult"),
}
}
#[test]
fn test_batch_create_edges() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE Person { name: 'A' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE Person { name: 'B' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute(&format!(
"BATCH CREATE EDGES [{{from: {}, to: {}, type: knows}}]",
a, b
))
.unwrap();
match result {
QueryResult::BatchResult(batch) => {
assert_eq!(batch.affected_count, 1);
},
_ => panic!("Expected BatchResult"),
}
}
#[test]
fn test_batch_delete_nodes() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE Temp").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE Temp").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute(&format!("BATCH DELETE NODES [{}, {}]", a, b))
.unwrap();
match result {
QueryResult::BatchResult(batch) => {
assert_eq!(batch.affected_count, 2);
},
_ => panic!("Expected BatchResult"),
}
}
#[test]
fn test_batch_delete_edges() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let edge_id = match router
.execute(&format!("EDGE CREATE {} -> {} : test_edge", a, b))
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute(&format!("BATCH DELETE EDGES [{}]", edge_id))
.unwrap();
match result {
QueryResult::BatchResult(batch) => {
assert_eq!(batch.affected_count, 1);
},
_ => panic!("Expected BatchResult"),
}
}
#[test]
fn test_batch_update_nodes() {
let router = QueryRouter::new();
let a = match router
.execute("NODE CREATE Person { name: 'Alice' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router
.execute("NODE CREATE Person { name: 'Bob' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute(&format!(
"BATCH UPDATE NODES [{{id: {}, age: 30}}, {{id: {}, age: 25}}]",
a, b
))
.unwrap();
match result {
QueryResult::BatchResult(batch) => {
assert_eq!(batch.operation, "UPDATE_NODES");
assert_eq!(batch.affected_count, 2);
},
_ => panic!("Expected BatchResult"),
}
}
#[test]
fn test_aggregate_node_property() {
let router = QueryRouter::new();
router.execute("NODE CREATE Person { age: 25 }").unwrap();
router.execute("NODE CREATE Person { age: 30 }").unwrap();
router.execute("NODE CREATE Person { age: 35 }").unwrap();
let result = router.execute("AGGREGATE NODE PROPERTY age SUM").unwrap();
match result {
QueryResult::Aggregate(AggregateResultValue::Sum(s)) => {
assert!((s - 90.0).abs() < 0.001);
},
_ => panic!("Expected Aggregate Sum result"),
}
}
#[test]
fn test_aggregate_node_property_avg() {
let router = QueryRouter::new();
router.execute("NODE CREATE Person { age: 20 }").unwrap();
router.execute("NODE CREATE Person { age: 40 }").unwrap();
let result = router.execute("AGGREGATE NODE PROPERTY age AVG").unwrap();
match result {
QueryResult::Aggregate(AggregateResultValue::Avg(a)) => {
assert!((a - 30.0).abs() < 0.001);
},
_ => panic!("Expected Aggregate Avg result"),
}
}
#[test]
fn test_aggregate_edge_property() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!(
"EDGE CREATE {} -> {} : conn {{ weight: 0.5 }}",
a, b
))
.unwrap();
let result = router
.execute("AGGREGATE EDGE PROPERTY weight SUM")
.unwrap();
assert!(matches!(
result,
QueryResult::Aggregate(AggregateResultValue::Sum(_))
));
}
#[test]
fn test_aggregate_edge_property_avg() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!(
"EDGE CREATE {} -> {} : conn {{ weight: 0.5 }}",
a, b
))
.unwrap();
let result = router
.execute("AGGREGATE EDGE PROPERTY weight AVG")
.unwrap();
assert!(matches!(
result,
QueryResult::Aggregate(AggregateResultValue::Avg(_))
));
}
#[test]
fn test_aggregate_edge_property_min() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!(
"EDGE CREATE {} -> {} : conn {{ score: 0.5 }}",
a, b
))
.unwrap();
let result = router.execute("AGGREGATE EDGE PROPERTY score MIN").unwrap();
assert!(matches!(
result,
QueryResult::Aggregate(AggregateResultValue::Min(_))
));
}
#[test]
fn test_aggregate_edge_property_max() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!(
"EDGE CREATE {} -> {} : conn {{ value: 0.75 }}",
a, b
))
.unwrap();
let result = router.execute("AGGREGATE EDGE PROPERTY value MAX").unwrap();
assert!(matches!(
result,
QueryResult::Aggregate(AggregateResultValue::Max(_))
));
}
#[test]
fn test_aggregate_edge_property_count() {
let router = QueryRouter::new();
let a = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let b = match router.execute("NODE CREATE nd").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!(
"EDGE CREATE {} -> {} : conn {{ prop: 0.1 }}",
a, b
))
.unwrap();
let result = router
.execute("AGGREGATE EDGE PROPERTY prop COUNT")
.unwrap();
assert!(matches!(
result,
QueryResult::Aggregate(AggregateResultValue::Count(_))
));
}
#[test]
fn parsed_embed_store_into_collection() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'doc1' [1.0, 2.0, 3.0] INTO my_collection")
.unwrap();
let result = router
.execute_parsed("EMBED GET 'doc1' INTO my_collection")
.unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn parsed_embed_delete_from_collection() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'to_delete' [1.0, 2.0] INTO test_coll")
.unwrap();
let result = router
.execute_parsed("EMBED DELETE 'to_delete' INTO test_coll")
.unwrap();
assert!(matches!(result, QueryResult::Count(1)));
let get_result = router.execute_parsed("EMBED GET 'to_delete' INTO test_coll");
assert!(get_result.is_err());
}
#[test]
fn parsed_similar_into_collection() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'vec_a' [1.0, 0.0, 0.0] INTO vectors")
.unwrap();
router
.execute_parsed("EMBED STORE 'vec_b' [0.9, 0.1, 0.0] INTO vectors")
.unwrap();
router
.execute_parsed("EMBED STORE 'vec_c' [0.0, 1.0, 0.0] INTO vectors")
.unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0, 0.0] LIMIT 3 INTO vectors")
.unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 3);
assert_eq!(results[0].key, "vec_a");
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_similar_into_collection_with_where() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'item_1' [1.0, 0.0, 0.0] INTO test_coll")
.unwrap();
router
.execute_parsed("EMBED STORE 'item_2' [0.9, 0.1, 0.0] INTO test_coll")
.unwrap();
router
.execute_parsed("EMBED STORE 'item_3' [0.0, 1.0, 0.0] INTO test_coll")
.unwrap();
let result = router.execute_parsed(
"SIMILAR [1.0, 0.0, 0.0] WHERE key CONTAINS 'item' LIMIT 3 INTO test_coll",
);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn parsed_similar_with_where_clause() {
let router = QueryRouter::new();
use std::collections::HashMap;
use tensor_store::ScalarValue;
let mut meta_a = HashMap::new();
meta_a.insert(
"category".to_string(),
tensor_store::TensorValue::Scalar(ScalarValue::String("science".to_string())),
);
router
.vector()
.store_embedding_with_metadata("item_a", vec![1.0, 0.0], meta_a)
.unwrap();
let mut meta_b = HashMap::new();
meta_b.insert(
"category".to_string(),
tensor_store::TensorValue::Scalar(ScalarValue::String("tech".to_string())),
);
router
.vector()
.store_embedding_with_metadata("item_b", vec![0.9, 0.1], meta_b)
.unwrap();
let mut meta_c = HashMap::new();
meta_c.insert(
"category".to_string(),
tensor_store::TensorValue::Scalar(ScalarValue::String("science".to_string())),
);
router
.vector()
.store_embedding_with_metadata("item_c", vec![0.8, 0.2], meta_c)
.unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0] LIMIT 10 WHERE category = 'science'")
.unwrap();
match result {
QueryResult::Similar(results) => {
assert_eq!(results.len(), 2);
for r in &results {
assert!(r.key == "item_a" || r.key == "item_c");
}
},
_ => panic!("Expected Similar"),
}
}
#[test]
fn parsed_embed_batch_into_collection() {
let router = QueryRouter::new();
let result = router
.execute_parsed("EMBED BATCH [('b1', [1.0, 2.0]), ('b2', [3.0, 4.0])] INTO batch_test")
.unwrap();
assert!(matches!(result, QueryResult::Count(2)));
let r1 = router.execute_parsed("EMBED GET 'b1' INTO batch_test");
let r2 = router.execute_parsed("EMBED GET 'b2' INTO batch_test");
assert!(r1.is_ok());
assert!(r2.is_ok());
}
#[test]
fn parsed_collection_isolation() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'shared_key' [1.0, 0.0] INTO coll_a")
.unwrap();
router
.execute_parsed("EMBED STORE 'shared_key' [0.0, 1.0] INTO coll_b")
.unwrap();
let result_a = router
.execute_parsed("EMBED GET 'shared_key' INTO coll_a")
.unwrap();
let result_b = router
.execute_parsed("EMBED GET 'shared_key' INTO coll_b")
.unwrap();
match (result_a, result_b) {
(QueryResult::Value(va), QueryResult::Value(vb)) => {
assert_ne!(va, vb);
},
_ => panic!("Expected Value results"),
}
}
#[test]
fn filter_condition_reexport_accessible() {
let filter = FilterCondition::Eq(
"status".to_string(),
FilterValue::String("active".to_string()),
);
assert!(matches!(filter, FilterCondition::Eq(_, _)));
let int_val = FilterValue::Int(42);
let float_val = FilterValue::Float(3.14);
let bool_val = FilterValue::Bool(true);
assert!(matches!(int_val, FilterValue::Int(42)));
assert!(matches!(float_val, FilterValue::Float(_)));
assert!(matches!(bool_val, FilterValue::Bool(true)));
}
#[test]
fn filter_strategy_reexport_accessible() {
let auto = FilterStrategy::Auto;
let pre = FilterStrategy::PreFilter;
let post = FilterStrategy::PostFilter;
assert!(matches!(auto, FilterStrategy::Auto));
assert!(matches!(pre, FilterStrategy::PreFilter));
assert!(matches!(post, FilterStrategy::PostFilter));
}
#[test]
fn filtered_search_config_reexport_accessible() {
let config = FilteredSearchConfig::default();
assert!(matches!(config.strategy, FilterStrategy::Auto));
let pre_config = FilteredSearchConfig::pre_filter();
assert!(matches!(pre_config.strategy, FilterStrategy::PreFilter));
let post_config = FilteredSearchConfig::post_filter();
assert!(matches!(post_config.strategy, FilterStrategy::PostFilter));
}
#[test]
fn expr_to_filter_condition_public() {
use neumann_parser::parse_expr;
let router = QueryRouter::new();
let expr = parse_expr("status = 'active'").unwrap();
let filter = router.expr_to_filter_condition(&expr).unwrap();
assert!(matches!(filter, FilterCondition::Eq(_, _)));
}
#[test]
fn expr_to_filter_condition_comparisons() {
use neumann_parser::parse_expr;
let router = QueryRouter::new();
let lt_expr = parse_expr("age < 30").unwrap();
let lt_filter = router.expr_to_filter_condition(<_expr).unwrap();
assert!(matches!(lt_filter, FilterCondition::Lt(_, _)));
let ge_expr = parse_expr("score >= 80").unwrap();
let ge_filter = router.expr_to_filter_condition(&ge_expr).unwrap();
assert!(matches!(ge_filter, FilterCondition::Ge(_, _)));
let ne_expr = parse_expr("status != 'deleted'").unwrap();
let ne_filter = router.expr_to_filter_condition(&ne_expr).unwrap();
assert!(matches!(ne_filter, FilterCondition::Ne(_, _)));
}
#[test]
fn expr_to_filter_condition_and_or() {
use neumann_parser::parse_expr;
let router = QueryRouter::new();
let and_expr = parse_expr("status = 'active' AND age > 18").unwrap();
let and_filter = router.expr_to_filter_condition(&and_expr).unwrap();
assert!(matches!(and_filter, FilterCondition::And(_, _)));
let or_expr = parse_expr("status = 'active' OR status = 'pending'").unwrap();
let or_filter = router.expr_to_filter_condition(&or_expr).unwrap();
assert!(matches!(or_filter, FilterCondition::Or(_, _)));
}
#[test]
fn expr_to_filter_value_types() {
use neumann_parser::parse_expr;
let router = QueryRouter::new();
let int_expr = parse_expr("42").unwrap();
let int_val = router.expr_to_filter_value(&int_expr).unwrap();
assert!(matches!(int_val, FilterValue::Int(42)));
let float_expr = parse_expr("3.14").unwrap();
let float_val = router.expr_to_filter_value(&float_expr).unwrap();
assert!(matches!(float_val, FilterValue::Float(_)));
let str_expr = parse_expr("'hello'").unwrap();
let str_val = router.expr_to_filter_value(&str_expr).unwrap();
assert!(matches!(str_val, FilterValue::String(_)));
let bool_expr = parse_expr("true").unwrap();
let bool_val = router.expr_to_filter_value(&bool_expr).unwrap();
assert!(matches!(bool_val, FilterValue::Bool(true)));
}
#[test]
fn expr_to_column_name_public() {
use neumann_parser::parse_expr;
let router = QueryRouter::new();
let ident_expr = parse_expr("column_name").unwrap();
let col = router.expr_to_column_name(&ident_expr).unwrap();
assert_eq!(col, "column_name");
}
#[test]
fn router_error_display_all_variants() {
let errors = vec![
(
RouterError::RelationalError("rel err".into()),
"Relational error: rel err",
),
(
RouterError::GraphError("graph err".into()),
"Graph error: graph err",
),
(
RouterError::VectorError("vec err".into()),
"Vector error: vec err",
),
(
RouterError::ParseError("parse err".into()),
"Parse error: parse err",
),
(
RouterError::UnknownCommand("cmd".into()),
"Unknown command: cmd",
),
(
RouterError::VaultError("vault err".into()),
"Vault error: vault err",
),
(
RouterError::CacheError("cache err".into()),
"Cache error: cache err",
),
(
RouterError::BlobError("blob err".into()),
"Blob error: blob err",
),
(
RouterError::CheckpointError("cp err".into()),
"Checkpoint error: cp err",
),
(
RouterError::ChainError("chain err".into()),
"Chain error: chain err",
),
(
RouterError::InvalidArgument("inv arg".into()),
"Invalid argument: inv arg",
),
(
RouterError::TypeMismatch("type mm".into()),
"Type mismatch: type mm",
),
(
RouterError::MissingArgument("miss arg".into()),
"Missing argument: miss arg",
),
(
RouterError::AuthenticationRequired,
"Authentication required: call SET IDENTITY before vault operations",
),
(
RouterError::NotFound("not found".into()),
"Not found: not found",
),
];
for (err, expected) in errors {
assert_eq!(format!("{}", err), expected);
}
}
#[test]
fn router_error_is_std_error() {
let err: Box<dyn std::error::Error> = Box::new(RouterError::ParseError("test".into()));
assert!(err.to_string().contains("Parse error"));
}
#[test]
fn router_error_from_unified_error_variants() {
let unified_rel = UnifiedError::RelationalError("rel".into());
let router_err: RouterError = unified_rel.into();
assert!(matches!(router_err, RouterError::RelationalError(_)));
let unified_graph = UnifiedError::GraphError("graph".into());
let router_err: RouterError = unified_graph.into();
assert!(matches!(router_err, RouterError::GraphError(_)));
let unified_vec = UnifiedError::VectorError("vec".into());
let router_err: RouterError = unified_vec.into();
assert!(matches!(router_err, RouterError::VectorError(_)));
let unified_not_found = UnifiedError::NotFound("key".into());
let router_err: RouterError = unified_not_found.into();
assert!(matches!(router_err, RouterError::VectorError(_)));
let unified_invalid = UnifiedError::InvalidOperation("op".into());
let router_err: RouterError = unified_invalid.into();
assert!(matches!(router_err, RouterError::InvalidArgument(_)));
let unified_batch = UnifiedError::BatchOperationFailed {
index: 0,
key: "k".into(),
cause: "c".into(),
};
let router_err: RouterError = unified_batch.into();
assert!(matches!(router_err, RouterError::VectorError(_)));
let unified_spatial = UnifiedError::SpatialError("bad bounds".into());
let router_err: RouterError = unified_spatial.into();
assert!(matches!(router_err, RouterError::InvalidArgument(_)));
}
#[test]
fn spatial_accessor_and_operations() {
let router = QueryRouter::new();
let spatial = router.spatial().clone();
assert_eq!(spatial.read().len(), 0);
router
.execute("SPATIAL INSERT 'park' BOUNDS 10.0 20.0 5.0 3.0")
.unwrap();
assert_eq!(spatial.read().len(), 1);
let guard = spatial.read();
let results = guard.query_within_radius_with_distances(10.0, 20.0, 50.0);
assert_eq!(results.len(), 1);
assert_eq!(results[0].0.data, "park");
}
#[test]
fn spatial_result_variant() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'obj' BOUNDS 1.0 2.0 1.0 1.0")
.unwrap();
let result = router
.execute("SPATIAL WITHIN 1.0 2.0 RADIUS 10.0")
.unwrap();
match &result {
QueryResult::Spatial(items) => {
assert!(!items.is_empty());
assert_eq!(items[0].key, "obj");
},
other => panic!("Expected Spatial, got: {other:?}"),
}
}
#[test]
fn query_result_as_rows_none() {
let result = QueryResult::Count(10);
assert!(result.as_rows().is_none());
let result = QueryResult::Value("test".into());
assert!(result.as_rows().is_none());
}
#[test]
fn query_result_as_count_variants() {
let result = QueryResult::Count(42);
assert_eq!(result.as_count(), Some(42));
let result = QueryResult::Rows(vec![]);
assert!(result.as_count().is_none());
let result = QueryResult::Ids(vec![1, 2, 3]);
assert!(result.as_count().is_none());
}
#[test]
fn query_result_as_value_variants() {
let result = QueryResult::Value("hello".into());
assert_eq!(result.as_value(), Some("hello"));
let result = QueryResult::Count(5);
assert!(result.as_value().is_none());
}
#[test]
fn query_result_is_empty_variants() {
assert!(QueryResult::Empty.is_empty());
assert!(!QueryResult::Rows(vec![]).is_empty());
assert!(!QueryResult::Rows(vec![Row {
id: 0,
values: vec![]
}])
.is_empty());
assert!(!QueryResult::Ids(vec![]).is_empty());
assert!(!QueryResult::Count(0).is_empty());
assert!(!QueryResult::Value("test".into()).is_empty());
assert!(!QueryResult::Nodes(vec![]).is_empty());
assert!(!QueryResult::Edges(vec![]).is_empty());
}
#[test]
fn query_result_debug_format() {
let result = QueryResult::Count(5);
let debug = format!("{:?}", result);
assert!(debug.contains("Count"));
}
#[test]
fn execute_empty_string() {
let router = QueryRouter::new();
let result = router.execute("");
assert!(result.is_err());
}
#[test]
fn execute_whitespace_variations() {
let router = QueryRouter::new();
let result = router.execute(" ");
assert!(result.is_err());
let result = router.execute("\t\t");
assert!(result.is_err());
}
#[test]
fn insert_with_null_value() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE nulltest (id int, name text)")
.unwrap();
router
.execute("INSERT INTO nulltest (id, name) VALUES (1, NULL)")
.unwrap();
let result = router.execute("SELECT * FROM nulltest").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn select_with_where_operators() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE ops (id int, val int)")
.unwrap();
router
.execute("INSERT INTO ops (id, val) VALUES (1, 10)")
.unwrap();
router
.execute("INSERT INTO ops (id, val) VALUES (2, 20)")
.unwrap();
router
.execute("INSERT INTO ops (id, val) VALUES (3, 30)")
.unwrap();
let result = router.execute("SELECT * FROM ops WHERE val < 25").unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 2);
}
let result = router.execute("SELECT * FROM ops WHERE val <= 20").unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 2);
}
let result = router.execute("SELECT * FROM ops WHERE val >= 20").unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 2);
}
let result = router.execute("SELECT * FROM ops WHERE val != 20").unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 2);
}
}
#[test]
fn select_with_and_or_conditions() {
let router = QueryRouter::new();
router.execute("CREATE TABLE logic (a int, b int)").unwrap();
router
.execute("INSERT INTO logic (a, b) VALUES (1, 1)")
.unwrap();
router
.execute("INSERT INTO logic (a, b) VALUES (1, 2)")
.unwrap();
router
.execute("INSERT INTO logic (a, b) VALUES (2, 1)")
.unwrap();
let result = router
.execute("SELECT * FROM logic WHERE a = 1 AND b = 1")
.unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 1);
}
let result = router
.execute("SELECT * FROM logic WHERE a = 1 OR b = 1")
.unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 3);
}
}
#[test]
fn node_create_with_various_property_types() {
let router = QueryRouter::new();
router.execute("NODE CREATE intnode { cnt: 42 }").unwrap();
router
.execute("NODE CREATE floatnode { value: 3.14 }")
.unwrap();
router
.execute("NODE CREATE boolnode { active: true }")
.unwrap();
router
.execute("NODE CREATE boolnode2 { active: false }")
.unwrap();
router
.execute("NODE CREATE strnode { name: 'hello world' }")
.unwrap();
}
#[test]
fn edge_operations_comprehensive() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE person { name: 'A' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE person { name: 'B' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n3 = match router.execute("NODE CREATE person { name: 'C' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let e1 = match router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let _e2 = router
.execute(&format!("EDGE CREATE {} -> {}", n2, n3))
.unwrap();
let result = router.execute(&format!("EDGE GET {}", e1)).unwrap();
assert!(matches!(result, QueryResult::Edges(_)));
}
#[test]
fn embed_and_similar_comprehensive() {
let router = QueryRouter::new();
router.execute("EMBED vec1 [1.0, 0.0, 0.0]").unwrap();
router.execute("EMBED vec2 [0.9, 0.1, 0.0]").unwrap();
router.execute("EMBED vec3 [0.0, 1.0, 0.0]").unwrap();
router.execute("EMBED vec4 [0.0, 0.0, 1.0]").unwrap();
let result = router.execute("SIMILAR vec1 TOP 3").unwrap();
if let QueryResult::Similar(results) = result {
assert!(results.len() <= 3);
}
let result = router.execute("SIMILAR [1.0, 0.0, 0.0] TOP 2").unwrap();
if let QueryResult::Similar(results) = result {
assert!(results.len() <= 2);
}
}
#[test]
fn aggregation_functions_comprehensive() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE agg (category string, value int)")
.unwrap();
router
.execute("INSERT INTO agg (category, value) VALUES ('A', 10)")
.unwrap();
router
.execute("INSERT INTO agg (category, value) VALUES ('A', 20)")
.unwrap();
router
.execute("INSERT INTO agg (category, value) VALUES ('B', 30)")
.unwrap();
let result = router.execute("SELECT COUNT(*) FROM agg").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
let result = router.execute("SELECT SUM(value) FROM agg").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
let result = router.execute("SELECT AVG(value) FROM agg").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
let result = router.execute("SELECT MIN(value) FROM agg").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
let result = router.execute("SELECT MAX(value) FROM agg").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn delete_from_table() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE deltest (id int, name string)")
.unwrap();
router
.execute("INSERT INTO deltest (id, name) VALUES (1, 'A')")
.unwrap();
router
.execute("INSERT INTO deltest (id, name) VALUES (2, 'B')")
.unwrap();
router
.execute("INSERT INTO deltest (id, name) VALUES (3, 'C')")
.unwrap();
router.execute("DELETE FROM deltest WHERE id = 2").unwrap();
let result = router.execute("SELECT * FROM deltest").unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 2);
}
}
#[test]
fn update_table_rows() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE updtest (id int, status string)")
.unwrap();
router
.execute("INSERT INTO updtest (id, status) VALUES (1, 'pending')")
.unwrap();
router
.execute("INSERT INTO updtest (id, status) VALUES (2, 'pending')")
.unwrap();
router
.execute("UPDATE updtest SET status='done' WHERE id = 1")
.unwrap();
let result = router
.execute("SELECT * FROM updtest WHERE id = 1")
.unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 1);
}
}
#[test]
fn drop_table_coverage() {
let router = QueryRouter::new();
router.execute("CREATE TABLE dropme (id int)").unwrap();
router
.execute("INSERT INTO dropme (id) VALUES (1)")
.unwrap();
router.execute("DROP TABLE dropme").unwrap();
let result = router.execute("SELECT * FROM dropme");
assert!(result.is_err());
}
#[test]
fn index_operations() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE indexed (id int, name string)")
.unwrap();
router
.execute("CREATE INDEX idx_name ON indexed(name)")
.unwrap();
router.execute("DROP INDEX ON indexed(name)").unwrap();
}
#[test]
fn join_operations() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE left_t (id int, val string)")
.unwrap();
router
.execute("CREATE TABLE right_t (id int, data string)")
.unwrap();
router
.execute("INSERT INTO left_t (id, val) VALUES (1, 'a')")
.unwrap();
router
.execute("INSERT INTO left_t (id, val) VALUES (2, 'b')")
.unwrap();
router
.execute("INSERT INTO right_t (id, data) VALUES (1, 'x')")
.unwrap();
router
.execute("INSERT INTO right_t (id, data) VALUES (3, 'y')")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM left_t JOIN right_t ON left_t.id = right_t.id")
.unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn path_operations() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE city { name: 'A' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE city { name: 'B' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n3 = match router.execute("NODE CREATE city { name: 'C' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap();
router
.execute(&format!("EDGE CREATE {} -> {}", n2, n3))
.unwrap();
let result = router.execute(&format!("PATH {} -> {}", n1, n3)).unwrap();
assert!(matches!(result, QueryResult::Path(_)));
}
#[test]
fn node_get_and_delete() {
let router = QueryRouter::new();
let id = match router
.execute("NODE CREATE test { name: 'ToDelete' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("NODE GET {}", id)).unwrap();
assert!(matches!(result, QueryResult::Nodes(_)));
router.execute(&format!("NODE DELETE {}", id)).unwrap();
let result = router.execute(&format!("NODE GET {}", id));
assert!(result.is_err());
}
#[test]
fn edge_get() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE a { x: 1 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE b { x: 2 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let edge_id = match router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("EDGE GET {}", edge_id)).unwrap();
assert!(matches!(result, QueryResult::Edges(_)));
}
#[test]
fn neighbors_command() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE hub { x: 1 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let n2 = match router.execute("NODE CREATE spoke { x: 2 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {} -> {}", n1, n2))
.unwrap();
let result = router.execute(&format!("NEIGHBORS {}", n1)).unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn show_tables_test() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE shown (id int, name string)")
.unwrap();
let result = router.execute("SHOW TABLES").unwrap();
assert!(matches!(result, QueryResult::TableList(_)));
}
#[test]
fn count_via_select() {
let router = QueryRouter::new();
router.execute("CREATE TABLE counted (id int)").unwrap();
router
.execute("INSERT INTO counted (id) VALUES (1)")
.unwrap();
router
.execute("INSERT INTO counted (id) VALUES (2)")
.unwrap();
let result = router.execute("SELECT COUNT(*) FROM counted").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn entity_create_get_update_delete() {
let router = QueryRouter::new();
router
.execute("ENTITY CREATE 'user:1' { name: 'Alice', age: '30' }")
.unwrap();
let result = router.execute("ENTITY GET 'user:1'").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
router
.execute("ENTITY UPDATE 'user:1' { name: 'Alicia', age: '31' }")
.unwrap();
router.execute("ENTITY DELETE 'user:1'").unwrap();
}
#[test]
fn entity_with_embedding() {
let router = QueryRouter::new();
router
.execute("ENTITY CREATE 'doc:1' { title: 'Test' } EMBEDDING [0.1, 0.2, 0.3]")
.unwrap();
let result = router.execute("ENTITY GET 'doc:1'").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
}
#[test]
fn entity_batch_create() {
let router = QueryRouter::new();
router.execute("ENTITY BATCH CREATE [{key: 'batch:1', name: 'First'}, {key: 'batch:2', name: 'Second'}]").unwrap();
let result = router.execute("ENTITY GET 'batch:1'").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
}
#[test]
fn entity_connect() {
let router = QueryRouter::new();
router
.execute("ENTITY CREATE 'user:alice' { name: 'Alice' }")
.unwrap();
router
.execute("ENTITY CREATE 'user:bob' { name: 'Bob' }")
.unwrap();
router
.execute("ENTITY CONNECT 'user:alice' -> 'user:bob' : follows")
.unwrap();
}
#[test]
fn find_nodes_edges_rows() {
let router = QueryRouter::new();
router.execute("NODE CREATE findtest { lbl: 'A' }").unwrap();
router.execute("NODE CREATE findtest { lbl: 'B' }").unwrap();
let result = router.execute("FIND NODES findtest").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
let result = router.execute("FIND EDGES").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
router.execute("CREATE TABLE findrows (x int)").unwrap();
router
.execute("INSERT INTO findrows (x) VALUES (1)")
.unwrap();
let result = router.execute("FIND ROWS FROM findrows").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
}
#[test]
fn cluster_commands() {
let router = QueryRouter::new();
let result = router.execute("CLUSTER STATUS");
assert!(result.is_ok());
let result = router.execute("CLUSTER NODES");
assert!(result.is_ok());
let result = router.execute("CLUSTER LEADER");
assert!(result.is_ok());
}
#[test]
fn chain_commands_basic() {
let router = QueryRouter::new();
let result = router.execute("CHAIN HEIGHT");
let _ = result;
let result = router.execute("CHAIN TIP");
let _ = result;
}
#[test]
fn cache_commands() {
let mut router = QueryRouter::new();
router.init_cache();
router
.execute("CACHE PUT 'test_prompt' 'test_response'")
.ok();
let _ = router.execute("CACHE GET 'test_prompt'");
let _ = router.execute("CACHE STATS");
}
#[test]
fn hnsw_build_command() {
let router = QueryRouter::new();
router.execute("EMBED h1 [1.0, 0.0, 0.0]").unwrap();
router.execute("EMBED h2 [0.0, 1.0, 0.0]").unwrap();
let result = router.execute("BUILD HNSW");
let _ = result;
}
#[test]
fn query_result_to_json_formats() {
let result = QueryResult::Count(42);
let json = result.to_json();
assert!(json.contains("42"));
let json_pretty = result.to_pretty_json();
assert!(json_pretty.contains("42"));
let row = Row {
id: 0,
values: vec![("name".to_string(), Value::String("test".to_string()))],
};
let result = QueryResult::Rows(vec![row]);
let json = result.to_json();
assert!(json.contains("name"));
}
#[test]
fn batch_operation_result_display() {
let batch = BatchOperationResult {
operation: "INSERT".to_string(),
affected_count: 5,
created_ids: Some(vec![1, 2, 3, 4, 5]),
};
let debug = format!("{:?}", batch);
assert!(debug.contains("INSERT"));
assert!(debug.contains("5"));
}
#[test]
fn similar_result_display() {
let similar = SimilarResult {
key: "test_key".to_string(),
score: 0.95,
};
let debug = format!("{:?}", similar);
assert!(debug.contains("test_key"));
}
#[test]
fn unified_result_display() {
let unified = UnifiedResult {
description: "Test results".to_string(),
items: vec![],
};
let debug = format!("{:?}", unified);
assert!(debug.contains("Test results"));
}
#[test]
fn error_conditions_comprehensive() {
let router = QueryRouter::new();
let result = router.execute("FOOBAR xyz");
assert!(result.is_err());
let result = router.execute("SELECT * FROM nonexistent");
assert!(result.is_err());
let result = router.execute("SELECT * FROM FROM");
assert!(result.is_err());
let result = router.execute("EMBED");
assert!(result.is_err());
let result = router.execute("EMBED bad [not,a,vector]");
assert!(result.is_err());
}
#[test]
fn constraint_operations() {
let router = QueryRouter::new();
let result = router.execute("CONSTRAINT ADD person name UNIQUE");
let _ = result;
let result = router.execute("CONSTRAINT LIST");
let _ = result;
let result = router.execute("CONSTRAINT REMOVE person name");
let _ = result;
}
#[test]
fn checkpoint_operations() {
let router = QueryRouter::new();
let result = router.execute("CHECKPOINT CREATE test_checkpoint");
let _ = result;
let result = router.execute("CHECKPOINT LIST");
let _ = result;
}
#[test]
fn rollback_operations() {
let router = QueryRouter::new();
let result = router.execute("ROLLBACK");
let _ = result;
}
#[test]
fn order_by_combinations() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE ordered (id int, name string, score int)")
.unwrap();
router
.execute("INSERT INTO ordered (id, name, score) VALUES (1, 'C', 30)")
.unwrap();
router
.execute("INSERT INTO ordered (id, name, score) VALUES (2, 'A', 10)")
.unwrap();
router
.execute("INSERT INTO ordered (id, name, score) VALUES (3, 'B', 20)")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM ordered ORDER BY name")
.unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
let result = router
.execute_parsed("SELECT * FROM ordered ORDER BY score DESC")
.unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
let result = router
.execute_parsed("SELECT * FROM ordered ORDER BY id LIMIT 2")
.unwrap();
if let QueryResult::Rows(rows) = result {
assert_eq!(rows.len(), 2);
}
}
#[test]
fn distinct_query() {
let router = QueryRouter::new();
router.execute("CREATE TABLE dups (cat string)").unwrap();
router
.execute("INSERT INTO dups (cat) VALUES ('A')")
.unwrap();
router
.execute("INSERT INTO dups (cat) VALUES ('A')")
.unwrap();
router
.execute("INSERT INTO dups (cat) VALUES ('B')")
.unwrap();
let result = router.execute("SELECT DISTINCT cat FROM dups").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn vector_collections() {
let router = QueryRouter::new();
router.execute("VECTOR COLLECTION CREATE test_coll").ok();
router.execute("EMBED coll_vec1 [1.0, 0.0, 0.0]").ok();
router
.execute("VECTOR COLLECTION ADD test_coll coll_vec1")
.ok();
let _ = router.execute("SIMILAR [1.0, 0.0, 0.0] IN test_coll TOP 5");
}
#[test]
fn metadata_operations() {
let router = QueryRouter::new();
router.execute("EMBED meta_vec [1.0, 0.0]").unwrap();
router
.execute("VECTOR META SET meta_vec category='test'")
.ok();
let _ = router.execute("VECTOR META GET meta_vec");
}
#[test]
fn transaction_commands() {
let router = QueryRouter::new();
let _ = router.execute("BEGIN");
let _ = router.execute("COMMIT");
let _ = router.execute("BEGIN");
let _ = router.execute("ROLLBACK TRANSACTION");
}
#[test]
fn explain_query() {
let router = QueryRouter::new();
router.execute("CREATE TABLE explained (id int)").unwrap();
let result = router.execute("EXPLAIN SELECT explained");
let _ = result;
}
#[test]
fn with_shared_store_and_engines() {
let store = TensorStore::new();
let router = QueryRouter::with_shared_store(store);
router.execute("CREATE TABLE shared (id int)").unwrap();
router
.execute("INSERT INTO shared (id) VALUES (1)")
.unwrap();
let result = router.execute("SELECT * FROM shared").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn query_router_accessors() {
let router = QueryRouter::new();
let _relational = router.relational();
let _graph = router.graph();
let _vector = router.vector();
assert!(router.cache().is_none());
assert!(router.vault().is_none());
}
#[test]
fn init_cache_and_vault() {
let mut router = QueryRouter::new();
router.init_cache();
assert!(router.cache().is_some());
router.init_vault(b"test_password_key").unwrap();
assert!(router.vault().is_some());
}
#[test]
fn execute_parsed_direct() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE parsed (id INT)")
.unwrap();
router
.execute_parsed("INSERT INTO parsed (id) VALUES (1)")
.unwrap();
let result = router.execute_parsed("SELECT * FROM parsed").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn runtime_accessor() {
let runtime = QueryRouter::create_runtime();
assert!(runtime.is_ok());
}
#[test]
fn test_chain_rollback_via_parsed() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("ROLLBACK CHAIN TO 0");
assert!(result.is_ok());
}
#[test]
fn test_chain_similar_via_parsed() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("CHAIN SIMILAR [1.0, 2.0, 3.0] LIMIT 10");
assert!(result.is_ok());
}
#[test]
fn test_chain_commit_via_parsed() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
router.execute_parsed("BEGIN CHAIN TRANSACTION").unwrap();
let result = router.execute_parsed("COMMIT CHAIN");
assert!(result.is_ok());
}
#[test]
fn test_cluster_disconnect_no_cluster() {
let router = QueryRouter::new();
let result = router.execute_parsed("CLUSTER DISCONNECT");
assert!(result.is_err());
}
#[test]
fn test_cluster_connect_error() {
let router = QueryRouter::new();
let result = router.execute_parsed("CLUSTER CONNECT 'localhost:7000'");
assert!(result.is_err());
}
#[test]
fn test_start_blob_after_init() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
let result = router.start_blob();
assert!(result.is_ok());
}
#[test]
fn test_entity_get_missing() {
let router = QueryRouter::new();
let result = router.execute_parsed("ENTITY GET 'nonexistent_key'");
let _ = result;
}
#[test]
fn test_describe_missing_table() {
let router = QueryRouter::new();
let result = router.execute("DESCRIBE missing_table");
assert!(result.is_err());
}
#[test]
fn test_select_from_missing_table() {
let router = QueryRouter::new();
let result = router.execute("SELECT * FROM missing_table");
assert!(result.is_err());
}
#[test]
fn test_insert_into_missing_table() {
let router = QueryRouter::new();
let result = router.execute("INSERT INTO missing_table (id) VALUES (1)");
assert!(result.is_err());
}
#[test]
fn test_update_missing_table() {
let router = QueryRouter::new();
let result = router.execute("UPDATE missing_table SET val = 1");
assert!(result.is_err());
}
#[test]
fn test_delete_from_missing_table() {
let router = QueryRouter::new();
let result = router.execute("DELETE FROM missing_table");
assert!(result.is_err());
}
#[test]
fn test_drop_missing_table() {
let router = QueryRouter::new();
let result = router.execute("DROP TABLE missing_table");
assert!(result.is_err());
}
#[test]
fn test_blob_get_missing() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let result = router.execute_parsed("BLOB GET 'missing_hash'");
assert!(result.is_err());
}
#[test]
fn test_cache_get_missing() {
let mut router = QueryRouter::new();
router.init_cache();
router.set_identity("user:test");
let result = router.execute_parsed("CACHE GET 'missing_key'");
let _ = result;
}
#[test]
fn test_empty_command_result() {
let router = QueryRouter::new();
let result = router.execute("");
let _ = result;
}
#[test]
fn test_whitespace_command_result() {
let router = QueryRouter::new();
let result = router.execute(" ");
let _ = result;
}
#[test]
fn test_comment_command_result() {
let router = QueryRouter::new();
let result = router.execute("-- this is a comment");
let _ = result;
}
#[test]
fn test_entity_update() {
let router = QueryRouter::new();
let create_result = router.execute_parsed("ENTITY CREATE 'user:1' { name: 'Alice' }");
assert!(create_result.is_ok());
let update_result =
router.execute_parsed("ENTITY UPDATE 'user:1' { name: 'Alicia', age: '30' }");
assert!(update_result.is_ok());
if let Ok(QueryResult::Value(msg)) = update_result {
assert!(msg.contains("updated"));
}
}
#[test]
fn test_entity_update_with_embedding() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'doc:1' { title: 'Test' } EMBEDDING [0.1, 0.2]")
.unwrap();
let result = router
.execute_parsed("ENTITY UPDATE 'doc:1' { title: 'Updated' } EMBEDDING [0.3, 0.4]");
assert!(result.is_ok());
}
#[test]
fn test_entity_update_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("ENTITY UPDATE 'nonexistent' { name: 'Test' }");
assert!(result.is_err());
}
#[test]
fn test_entity_delete() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'user:2' { name: 'Bob' }")
.unwrap();
let delete_result = router.execute_parsed("ENTITY DELETE 'user:2'");
assert!(delete_result.is_ok());
if let Ok(QueryResult::Value(msg)) = delete_result {
assert!(msg.contains("deleted"));
}
}
#[test]
fn test_entity_delete_nonexistent() {
let router = QueryRouter::new();
let result = router.execute_parsed("ENTITY DELETE 'nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_entity_crud_flow() {
let router = QueryRouter::new();
let create = router.execute_parsed("ENTITY CREATE 'item:1' { status: 'new' }");
assert!(create.is_ok());
let get = router.execute_parsed("ENTITY GET 'item:1'");
assert!(get.is_ok());
let update = router.execute_parsed("ENTITY UPDATE 'item:1' { status: 'active' }");
assert!(update.is_ok());
let delete = router.execute_parsed("ENTITY DELETE 'item:1'");
assert!(delete.is_ok());
let get_after = router.execute_parsed("ENTITY GET 'item:1'");
assert!(get_after.is_err());
}
#[test]
fn test_find_rows_from_table() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE products (name string, price int)")
.unwrap();
router
.execute("INSERT INTO products (name, price) VALUES ('Widget', 100)")
.unwrap();
router
.execute("INSERT INTO products (name, price) VALUES ('Gadget', 200)")
.unwrap();
let result = router.execute_parsed("FIND ROWS FROM products");
assert!(result.is_ok());
if let Ok(QueryResult::Unified(unified)) = result {
assert_eq!(unified.items.len(), 2);
}
}
#[test]
fn test_find_rows_with_where() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE items (id int, active bool)")
.unwrap();
router
.execute("INSERT INTO items (id, active) VALUES (1, true)")
.unwrap();
router
.execute("INSERT INTO items (id, active) VALUES (2, false)")
.unwrap();
router
.execute("INSERT INTO items (id, active) VALUES (3, true)")
.unwrap();
let result = router.execute_parsed("FIND ROWS FROM items WHERE active = TRUE");
assert!(result.is_ok());
if let Ok(QueryResult::Unified(unified)) = result {
assert_eq!(unified.items.len(), 2);
}
}
#[test]
fn test_find_rows_with_limit() {
let router = QueryRouter::new();
router.execute("CREATE TABLE numbers (val int)").unwrap();
for i in 1..=10 {
router
.execute(&format!("INSERT INTO numbers (val) VALUES ({i})"))
.unwrap();
}
let result = router.execute_parsed("FIND ROWS FROM numbers LIMIT 3");
assert!(result.is_ok());
if let Ok(QueryResult::Unified(unified)) = result {
assert_eq!(unified.items.len(), 3);
}
}
#[test]
fn test_find_rows_missing_table() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND ROWS FROM nonexistent");
assert!(result.is_err());
}
#[test]
fn test_find_node_similar_to_basic() {
let router = QueryRouter::new();
router
.execute_parsed(
"ENTITY CREATE 'user:alice' {name: 'Alice', role: 'engineer'} EMBEDDING [1.0, 0.0, 0.0]",
)
.unwrap();
router
.execute_parsed(
"ENTITY CREATE 'user:bob' {name: 'Bob', role: 'engineer'} EMBEDDING [0.9, 0.1, 0.0]",
)
.unwrap();
let result = router
.execute_parsed("FIND NODE SIMILAR TO 'user:alice'")
.unwrap();
match result {
QueryResult::Unified(unified) => {
assert!(!unified.items.is_empty());
},
other => panic!("Expected Unified, got {other:?}"),
}
}
#[test]
fn test_find_node_connected_to_basic() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'user:alice' {name: 'Alice'}")
.unwrap();
router
.execute_parsed("ENTITY CREATE 'user:bob' {name: 'Bob'}")
.unwrap();
router
.execute_parsed("ENTITY CREATE 'user:carol' {name: 'Carol'}")
.unwrap();
router
.execute_parsed("ENTITY CONNECT 'user:alice' -> 'user:bob' : reports_to")
.unwrap();
let result = router
.execute_parsed("FIND NODE CONNECTED TO 'user:alice'")
.unwrap();
match result {
QueryResult::Unified(unified) => {
assert_eq!(unified.items.len(), 1);
let item = &unified.items[0];
assert!(item
.data
.get("entity_key")
.is_some_and(|ek| ek == "user:bob"));
},
other => panic!("Expected Unified, got {other:?}"),
}
}
#[test]
fn test_find_node_hero_query() {
let router = QueryRouter::new();
router
.execute_parsed(
"ENTITY CREATE 'user:alice' {name: 'Alice', role: 'engineer'} EMBEDDING [1.0, 0.0, 0.0]",
)
.unwrap();
router
.execute_parsed(
"ENTITY CREATE 'user:bob' {name: 'Bob', role: 'engineer'} EMBEDDING [0.9, 0.1, 0.0]",
)
.unwrap();
router
.execute_parsed(
"ENTITY CREATE 'user:carol' {name: 'Carol', role: 'manager'} EMBEDDING [0.0, 1.0, 0.0]",
)
.unwrap();
router
.execute_parsed("ENTITY CREATE 'user:hub' {name: 'Hub', role: 'director'}")
.unwrap();
router
.execute_parsed("ENTITY CONNECT 'user:hub' -> 'user:alice' : manages")
.unwrap();
router
.execute_parsed("ENTITY CONNECT 'user:hub' -> 'user:bob' : manages")
.unwrap();
router
.execute_parsed("ENTITY CONNECT 'user:hub' -> 'user:carol' : manages")
.unwrap();
let result = router
.execute_parsed(
"FIND NODE WHERE role = 'engineer' SIMILAR TO 'user:alice' CONNECTED TO 'user:hub'",
)
.unwrap();
match result {
QueryResult::Unified(unified) => {
assert_eq!(unified.items.len(), 2);
assert!(unified.items[0].score.is_some());
assert!(unified.items[1].score.is_some());
assert!(unified.items[0].score.unwrap() >= unified.items[1].score.unwrap());
},
other => panic!("Expected Unified, got {other:?}"),
}
}
#[test]
fn test_find_edge_similar_to_rejects() {
let router = QueryRouter::new();
let result = router.execute_parsed("FIND EDGE follows SIMILAR TO 'user:alice'");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("only supported with FIND NODE"));
}
#[test]
fn test_find_node_similar_connected_with_limit() {
let router = QueryRouter::new();
for i in 0..5 {
router
.execute_parsed(&format!(
"ENTITY CREATE 'user:{i}' {{name: 'User{i}'}} EMBEDDING [{}.0, 0.0, 0.0]",
i + 1
))
.unwrap();
}
router
.execute_parsed("ENTITY CREATE 'user:hub' {name: 'Hub'}")
.unwrap();
for i in 0..5 {
router
.execute_parsed(&format!(
"ENTITY CONNECT 'user:hub' -> 'user:{i}' : manages"
))
.unwrap();
}
let result = router
.execute_parsed("FIND NODE SIMILAR TO 'user:0' CONNECTED TO 'user:hub' LIMIT 2")
.unwrap();
match result {
QueryResult::Unified(unified) => {
assert!(unified.items.len() <= 2);
},
other => panic!("Expected Unified, got {other:?}"),
}
}
#[test]
fn test_paginated_query_first_page() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE paged_users (name string, age int)")
.unwrap();
for i in 1..=50 {
router
.execute(&format!(
"INSERT INTO paged_users (name, age) VALUES ('user{i}', {i})"
))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(10)
.with_count_total(true);
let result = router.execute_paginated("SELECT * FROM paged_users", options);
assert!(result.is_ok());
let paged = result.unwrap();
assert_eq!(paged.page_size, 10);
assert_eq!(paged.total_count, Some(50));
assert!(paged.has_more);
assert!(paged.next_cursor.is_some());
assert!(paged.prev_cursor.is_none());
let rows = unwrap_qr_rows(paged.result);
assert_eq!(rows.len(), 10);
}
#[test]
fn test_paginated_query_with_cursor() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE cursor_test (val int)")
.unwrap();
for i in 1..=25 {
router
.execute(&format!("INSERT INTO cursor_test (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(10)
.with_count_total(true);
let page1 = router
.execute_paginated("SELECT * FROM cursor_test", options)
.unwrap();
assert!(page1.next_cursor.is_some());
let cursor = page1.next_cursor.unwrap();
let options2 = PaginationOptions::new()
.with_cursor(cursor)
.with_page_size(10)
.with_count_total(true);
let page2 = router
.execute_paginated("SELECT * FROM cursor_test", options2)
.unwrap();
assert!(page2.has_more); assert!(page2.prev_cursor.is_some());
if let QueryResult::Rows(rows) = page2.result {
assert_eq!(rows.len(), 10);
}
}
#[test]
fn test_paginated_query_last_page() {
let router = QueryRouter::new();
router.execute("CREATE TABLE last_page (val int)").unwrap();
for i in 1..=15 {
router
.execute(&format!("INSERT INTO last_page (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(20)
.with_count_total(true);
let result = router
.execute_paginated("SELECT * FROM last_page", options)
.unwrap();
assert!(!result.has_more);
assert!(result.next_cursor.is_none());
assert_eq!(result.total_count, Some(15));
if let QueryResult::Rows(rows) = result.result {
assert_eq!(rows.len(), 15);
}
}
#[test]
fn test_paginated_query_nodes() {
let router = QueryRouter::new();
for i in 1..=30 {
router
.execute(&format!("NODE CREATE TestNode {{ id: {i} }}"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(10)
.with_count_total(true);
let result = router.execute_paginated("NODE LIST TestNode", options);
assert!(result.is_ok());
let paged = result.unwrap();
assert!(paged.has_more);
if let QueryResult::Nodes(nodes) = paged.result {
assert_eq!(nodes.len(), 10);
}
}
#[test]
fn test_paginated_query_invalid_cursor() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE invalid_cursor (val int)")
.unwrap();
let options = PaginationOptions::new().with_cursor("invalid-cursor-token".to_string());
let result = router.execute_paginated("SELECT * FROM invalid_cursor", options);
assert!(result.is_err());
}
#[test]
fn test_paginated_query_cursor_mismatch() {
let router = QueryRouter::new();
router.execute("CREATE TABLE mismatch1 (val int)").unwrap();
router.execute("CREATE TABLE mismatch2 (val int)").unwrap();
for i in 1..=10 {
router
.execute(&format!("INSERT INTO mismatch1 (val) VALUES ({i})"))
.unwrap();
router
.execute(&format!("INSERT INTO mismatch2 (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(5)
.with_count_total(true);
let page1 = router
.execute_paginated("SELECT * FROM mismatch1", options)
.unwrap();
let cursor = page1.next_cursor.expect("Should have next cursor");
let options2 = PaginationOptions::new().with_cursor(cursor);
let result = router.execute_paginated("SELECT * FROM mismatch2", options2);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Cursor query does not match"));
}
#[test]
fn test_close_cursor() {
let router = QueryRouter::new();
router.execute("CREATE TABLE close_test (val int)").unwrap();
for i in 1..=20 {
router
.execute(&format!("INSERT INTO close_test (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(5)
.with_count_total(true);
let page1 = router
.execute_paginated("SELECT * FROM close_test", options)
.unwrap();
let cursor = page1.next_cursor.expect("Should have next cursor");
let closed = router.close_cursor(&cursor).unwrap();
assert!(closed);
}
#[test]
fn test_paginated_non_paginatable_result() {
let router = QueryRouter::new();
router.execute("CREATE TABLE non_page (val int)").unwrap();
let options = PaginationOptions::new().with_page_size(10);
let result = router.execute_paginated("CREATE TABLE another_table (x int)", options);
assert!(result.is_err());
}
#[test]
fn test_pagination_options_builder() {
let options = PaginationOptions::new()
.with_page_size(50)
.with_count_total(true)
.with_cursor_ttl(std::time::Duration::from_secs(60));
assert_eq!(options.page_size, Some(50));
assert!(options.count_total);
assert_eq!(options.cursor_ttl, Some(std::time::Duration::from_secs(60)));
}
#[test]
fn test_paged_query_result_fields() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE fields_test (val int)")
.unwrap();
for i in 1..=5 {
router
.execute(&format!("INSERT INTO fields_test (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(3)
.with_count_total(true);
let result = router
.execute_paginated("SELECT * FROM fields_test", options)
.unwrap();
assert_eq!(result.page_size, 3);
assert_eq!(result.total_count, Some(5));
assert!(result.has_more);
assert!(result.next_cursor.is_some());
assert!(result.prev_cursor.is_none());
}
#[test]
fn test_edge_list_parsed() {
let router = QueryRouter::new();
for i in 1..=10 {
router
.execute(&format!("NODE CREATE Person {{ id: {i} }}"))
.unwrap();
}
for i in 1..=25 {
let from = ((i - 1) % 10) + 1;
let to = (i % 10) + 1;
router
.execute(&format!("EDGE CREATE {from} -> {to} : KNOWS"))
.unwrap();
}
let full_result = router.execute_parsed("EDGE LIST KNOWS").unwrap();
let edges = unwrap_qr_edges(full_result);
assert_eq!(edges.len(), 25);
}
#[test]
fn test_paginated_query_similar() {
let router = QueryRouter::new();
for i in 1..=30 {
let vals = (1..=4)
.map(|j| format!("{}", (i * j) as f32 / 100.0))
.collect::<Vec<_>>()
.join(", ");
router.execute(&format!("EMBED key{i} [{vals}]")).unwrap();
}
let options = PaginationOptions::new()
.with_page_size(5)
.with_count_total(true);
let result = router.execute_paginated("SIMILAR key1 TOP 20", options);
assert!(result.is_ok());
let paged = result.unwrap();
let items = unwrap_qr_similar(paged.result);
assert!(items.len() <= 5);
}
#[test]
fn test_paginated_query_unified() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE unified_test (name string, score int)")
.unwrap();
for i in 1..=20 {
router
.execute(&format!(
"INSERT INTO unified_test (name, score) VALUES ('item{i}', {i})"
))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(5)
.with_count_total(true);
let result = router.execute_paginated("FIND ROWS FROM unified_test", options);
assert!(result.is_ok());
let paged = result.unwrap();
assert_eq!(paged.total_count, Some(20));
let unified = unwrap_qr_unified(paged.result);
assert_eq!(unified.items.len(), 5);
}
#[test]
fn test_close_cursor_not_found() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE close_not_found (val int)")
.unwrap();
for i in 1..=20 {
router
.execute(&format!("INSERT INTO close_not_found (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(5)
.with_count_total(true);
let page1 = router
.execute_paginated("SELECT * FROM close_not_found", options)
.unwrap();
let cursor = page1.next_cursor.expect("Should have next cursor");
let closed1 = router.close_cursor(&cursor).unwrap();
assert!(closed1);
let closed2 = router.close_cursor(&cursor).unwrap();
assert!(!closed2);
}
#[test]
fn test_cursor_store_accessor() {
let router = QueryRouter::new();
let store = router.cursor_store();
assert!(store.len() <= 1);
}
#[test]
fn test_paginated_with_custom_ttl() {
let router = QueryRouter::new();
router.execute("CREATE TABLE ttl_test (val int)").unwrap();
for i in 1..=20 {
router
.execute(&format!("INSERT INTO ttl_test (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(5)
.with_count_total(true)
.with_cursor_ttl(std::time::Duration::from_secs(120));
let result = router
.execute_paginated("SELECT * FROM ttl_test", options)
.unwrap();
assert!(result.has_more);
assert!(result.next_cursor.is_some());
}
#[test]
fn test_paginated_without_count_total() {
let router = QueryRouter::new();
router.execute("CREATE TABLE no_count (val int)").unwrap();
for i in 1..=10 {
router
.execute(&format!("INSERT INTO no_count (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new().with_page_size(5);
let result = router
.execute_paginated("SELECT * FROM no_count", options)
.unwrap();
assert_eq!(result.total_count, None);
assert!(!result.has_more);
}
#[test]
fn test_router_error_cursor_display() {
let err = RouterError::CursorError("test cursor error".to_string());
let display = format!("{}", err);
assert!(display.contains("Cursor error"));
assert!(display.contains("test cursor error"));
}
#[test]
fn test_paged_query_result_debug() {
let paged = PagedQueryResult {
result: QueryResult::Empty,
next_cursor: Some("next".to_string()),
prev_cursor: Some("prev".to_string()),
total_count: Some(100),
has_more: true,
page_size: 10,
};
let debug = format!("{:?}", paged);
assert!(debug.contains("PagedQueryResult"));
assert!(debug.contains("next"));
assert!(debug.contains("prev"));
}
#[test]
fn test_pagination_options_default() {
let options = PaginationOptions::default();
assert!(options.cursor.is_none());
assert!(options.page_size.is_none());
assert!(!options.count_total);
assert!(options.cursor_ttl.is_none());
}
#[test]
fn test_paginated_max_ttl_capped() {
let router = QueryRouter::new();
router.execute("CREATE TABLE max_ttl (val int)").unwrap();
for i in 1..=20 {
router
.execute(&format!("INSERT INTO max_ttl (val) VALUES ({i})"))
.unwrap();
}
let options = PaginationOptions::new()
.with_page_size(5)
.with_count_total(true)
.with_cursor_ttl(std::time::Duration::from_secs(7200)); let result = router
.execute_paginated("SELECT * FROM max_ttl", options)
.unwrap();
assert!(result.next_cursor.is_some());
}
#[test]
fn test_paginated_third_page_with_prev() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE three_pages (val int)")
.unwrap();
for i in 1..=30 {
router
.execute(&format!("INSERT INTO three_pages (val) VALUES ({i})"))
.unwrap();
}
let opts1 = PaginationOptions::new()
.with_page_size(10)
.with_count_total(true);
let page1 = router
.execute_paginated("SELECT * FROM three_pages", opts1)
.unwrap();
assert!(page1.prev_cursor.is_none());
let cursor1 = page1.next_cursor.unwrap();
let opts2 = PaginationOptions::new()
.with_cursor(cursor1)
.with_page_size(10)
.with_count_total(true);
let page2 = router
.execute_paginated("SELECT * FROM three_pages", opts2)
.unwrap();
assert!(page2.prev_cursor.is_some());
let cursor2 = page2.next_cursor.unwrap();
let opts3 = PaginationOptions::new()
.with_cursor(cursor2)
.with_page_size(10)
.with_count_total(true);
let page3 = router
.execute_paginated("SELECT * FROM three_pages", opts3)
.unwrap();
assert!(page3.prev_cursor.is_some()); assert!(!page3.has_more); }
#[test]
fn test_cursor_error_from_conversion() {
let cursor_err = CursorError::InvalidToken("bad token".to_string());
let router_err: RouterError = cursor_err.into();
assert!(matches!(router_err, RouterError::CursorError(_)));
}
#[test]
fn test_cluster_status_no_cluster() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let stmt = parser::parse("CLUSTER STATUS").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let msg = unwrap_qr_value(result);
assert!(msg.contains("single-node"));
}
#[test]
fn test_cluster_nodes_no_cluster() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let stmt = parser::parse("CLUSTER NODES").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let msg = unwrap_qr_value(result);
assert!(msg.contains("single-node"));
}
#[test]
fn test_cluster_leader_no_cluster() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let stmt = parser::parse("CLUSTER LEADER").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let msg = unwrap_qr_value(result);
assert!(msg.contains("single-node"));
}
#[test]
fn test_cluster_connect_error_message() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let stmt = parser::parse("CLUSTER CONNECT '127.0.0.1:9300'").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
if let Err(RouterError::InvalidArgument(msg)) = result {
assert!(msg.contains("shell"));
}
}
#[test]
fn test_cluster_disconnect_with_no_cluster() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let stmt = parser::parse("CLUSTER DISCONNECT").unwrap();
let result = router.execute_statement(&stmt);
assert!(result.is_err());
if let Err(RouterError::InvalidArgument(msg)) = result {
assert!(msg.contains("Not connected"));
}
}
#[test]
fn test_chain_analyze_transitions() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("ANALYZE CODEBOOK TRANSITIONS").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::TransitionAnalysis(analysis)) = result {
assert_eq!(analysis.total_transitions, 0);
assert_eq!(analysis.valid_transitions, 0);
} else {
panic!("expected TransitionAnalysis result");
}
}
#[test]
fn test_chain_show_codebook_global_via_exec() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("SHOW CODEBOOK GLOBAL").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Codebook(info)) = result {
assert_eq!(info.scope, "global");
assert!(info.domain.is_none());
} else {
panic!("expected Codebook result");
}
}
#[test]
fn test_chain_show_codebook_local_via_exec() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("SHOW CODEBOOK LOCAL 'my_domain'").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Codebook(info)) = result {
assert_eq!(info.scope, "local");
assert_eq!(info.domain.as_deref(), Some("my_domain"));
} else {
panic!("expected Codebook result");
}
}
#[test]
fn test_chain_similar_empty_result() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("CHAIN SIMILAR [1.0, 2.0] LIMIT 5").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Similar(items)) = result {
assert!(items.is_empty());
} else {
panic!("expected Similar result");
}
}
#[test]
fn test_chain_commit_via_exec() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("COMMIT CHAIN").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::Committed { height, .. }) = result {
assert_eq!(height, 0);
} else {
panic!("expected Committed result");
}
}
#[test]
fn test_chain_rollback_via_exec() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
router.set_identity("user:test");
let stmt = parser::parse("ROLLBACK CHAIN TO 5").unwrap();
let result = router.execute_statement(&stmt).unwrap();
if let QueryResult::Chain(ChainResult::RolledBack { to_height }) = result {
assert_eq!(to_height, 5);
} else {
panic!("expected RolledBack result");
}
}
#[test]
fn test_has_checkpoint_false() {
let router = QueryRouter::new();
assert!(!router.has_checkpoint());
}
#[test]
fn test_has_hnsw_index_false() {
let router = QueryRouter::new();
assert!(!router.has_hnsw_index());
}
#[test]
fn test_hnsw_generation_starts_fresh() {
let router = QueryRouter::new();
assert!(router.hnsw_is_fresh());
}
#[test]
fn test_hnsw_generation_stale_after_embed_store() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
assert!(router.has_hnsw_index());
router
.execute_parsed("EMBED STORE 'v2' [4.0, 5.0, 6.0]")
.unwrap();
assert!(!router.hnsw_is_fresh());
}
#[test]
fn test_hnsw_generation_fresh_after_rebuild() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
router
.execute_parsed("EMBED STORE 'v2' [4.0, 5.0, 6.0]")
.unwrap();
assert!(!router.hnsw_is_fresh());
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
}
#[test]
fn test_hnsw_generation_not_bumped_for_named_collection() {
let mut router = QueryRouter::new();
router
.vector
.create_collection(
"test_coll",
vector_engine::VectorCollectionConfig::default().with_dimension(3),
)
.unwrap();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
router
.execute_parsed("EMBED STORE 'v2' [4.0, 5.0, 6.0] INTO test_coll")
.unwrap();
assert!(router.hnsw_is_fresh());
}
#[test]
fn test_hnsw_stale_after_embed_delete() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
router.execute_parsed("EMBED DELETE 'v1'").unwrap();
assert!(!router.hnsw_is_fresh());
}
#[test]
fn test_hnsw_stale_after_entity_create_with_embedding() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
router
.execute_parsed("ENTITY CREATE 'e1' { name: 'test' } EMBEDDING [1.0, 2.0, 3.0]")
.unwrap();
assert!(!router.hnsw_is_fresh());
}
#[test]
fn test_entity_get_via_unified() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'e1' { name: 'alice' } EMBEDDING [1.0, 2.0, 3.0]")
.unwrap();
let result = router.execute_parsed("ENTITY GET 'e1'").unwrap();
match result {
QueryResult::Unified(u) => {
assert_eq!(u.items.len(), 1);
assert_eq!(u.items[0].id, "e1");
},
other => panic!("Expected Unified result, got {other:?}"),
}
}
#[test]
fn test_entity_get_not_found() {
let router = QueryRouter::new();
let result = router.execute_parsed("ENTITY GET 'nonexistent'");
assert!(result.is_err());
}
#[test]
fn test_hnsw_stale_after_entity_batch_with_embeddings() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
router
.execute_parsed(
"ENTITY BATCH CREATE [\
{key: 'b1', name: 'one', embedding: [1.0, 2.0, 3.0]}, \
{key: 'b2', name: 'two', embedding: [4.0, 5.0, 6.0]}]",
)
.unwrap();
assert!(!router.hnsw_is_fresh());
}
#[test]
fn test_entity_update_with_embedding_bumps_generation() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router
.execute_parsed("ENTITY CREATE 'e1' { name: 'alice' } EMBEDDING [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
router
.execute_parsed("ENTITY UPDATE 'e1' { name: 'bob' } EMBEDDING [4.0, 5.0, 6.0]")
.unwrap();
assert!(!router.hnsw_is_fresh());
}
#[test]
fn test_entity_delete_bumps_generation() {
let mut router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'v1' [1.0, 2.0, 3.0]")
.unwrap();
router
.execute_parsed("ENTITY CREATE 'e1' { name: 'alice' } EMBEDDING [1.0, 2.0, 3.0]")
.unwrap();
router.build_vector_index().unwrap();
assert!(router.hnsw_is_fresh());
router.execute_parsed("ENTITY DELETE 'e1'").unwrap();
assert!(!router.hnsw_is_fresh());
}
#[test]
fn test_entity_connect_via_parsed() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'e1' { name: 'alice' }")
.unwrap();
router
.execute_parsed("ENTITY CREATE 'e2' { name: 'bob' }")
.unwrap();
let result = router
.execute_parsed("ENTITY CONNECT 'e1' -> 'e2' : knows")
.unwrap();
match result {
QueryResult::Value(msg) => {
assert!(msg.contains("Connected"), "Expected connect message: {msg}");
},
other => panic!("Expected Value result, got {other:?}"),
}
}
#[test]
fn test_create_and_drop_index_via_parsed() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE idx_test (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("CREATE INDEX idx_name ON idx_test (name)")
.unwrap();
router
.execute_parsed("DROP INDEX ON idx_test (name)")
.unwrap();
router
.execute_parsed("DROP INDEX IF EXISTS ON idx_test (name)")
.unwrap();
}
#[test]
fn test_drop_table_via_parsed() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE drop_test (id INT)")
.unwrap();
router.execute_parsed("DROP TABLE drop_test").unwrap();
}
#[test]
fn test_show_tables_and_describe_via_parsed() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE desc_test (id INT, name TEXT)")
.unwrap();
let result = router.execute_parsed("SHOW TABLES").unwrap();
match &result {
QueryResult::TableList(tables) => assert!(tables.contains(&"desc_test".to_string())),
other => panic!("Expected TableList, got {other:?}"),
}
let result = router.execute_parsed("DESCRIBE TABLE desc_test").unwrap();
assert!(!matches!(result, QueryResult::Empty));
}
#[test]
fn test_legacy_node_list_with_label_filter() {
let store = tensor_store::TensorStore::new();
let router = QueryRouter::with_shared_store(store);
let _id1 = router.graph.create_node("person", HashMap::new()).unwrap();
let id2 = router.graph.create_node("place", HashMap::new()).unwrap();
let _id3 = router.graph.create_node("person", HashMap::new()).unwrap();
let result = router.execute("NODE LIST person").unwrap();
match result {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes.len(), 2);
for n in &nodes {
assert!(n.label.contains("person"));
}
},
other => panic!("Expected Nodes, got {other:?}"),
}
let result = router.execute("NODE LIST").unwrap();
match result {
QueryResult::Nodes(nodes) => assert!(nodes.len() >= 3),
other => panic!("Expected Nodes, got {other:?}"),
}
let result = router.execute("NODE LIST place").unwrap();
match result {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].id, id2);
},
other => panic!("Expected Nodes, got {other:?}"),
}
}
#[test]
fn test_tls_cert_path_none() {
let router = QueryRouter::new();
assert!(router.tls_cert_path().is_none());
}
#[test]
fn test_chain_accessor_none() {
let router = QueryRouter::new();
assert!(router.chain().is_none());
}
#[test]
fn test_chain_accessor_some() {
let mut router = QueryRouter::new();
router.init_chain("test_node").unwrap();
assert!(router.chain().is_some());
}
#[test]
fn test_ensure_chain_auto_init() {
let mut router = QueryRouter::new();
assert!(router.chain().is_none());
let chain = router.ensure_chain();
assert!(chain.is_ok());
assert!(router.chain().is_some());
}
#[test]
fn test_set_confirmation_handler_no_checkpoint() {
struct DummyHandler;
impl ConfirmationHandler for DummyHandler {
fn confirm(&self, _op: &DestructiveOp, _preview: &OperationPreview) -> bool {
true
}
}
let router = QueryRouter::new();
let handler: Arc<dyn ConfirmationHandler> = Arc::new(DummyHandler);
let result = router.set_confirmation_handler(handler);
assert!(result.is_err());
if let Err(RouterError::CheckpointError(msg)) = result {
assert!(msg.contains("not initialized"));
}
}
#[test]
fn test_paginated_query_edges() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let n1 = match router
.execute("NODE CREATE person { name: 'Alice' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
let n2 = match router
.execute("NODE CREATE person { name: 'Bob' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
let n3 = match router
.execute("NODE CREATE person { name: 'Carol' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : knows"))
.unwrap();
router
.execute(&format!("EDGE CREATE {n2} -> {n3} : knows"))
.unwrap();
router
.execute(&format!("EDGE CREATE {n1} -> {n3} : knows"))
.unwrap();
let result = router.execute_parsed("EDGE LIST").unwrap();
let edges = unwrap_qr_edges(result);
assert_eq!(edges.len(), 3);
}
#[test]
fn test_chain_error_conversion() {
let chain_err = tensor_chain::ChainError::ValidationFailed("bad block".to_string());
let router_err: RouterError = chain_err.into();
if let RouterError::ChainError(msg) = router_err {
assert!(msg.contains("bad block"));
} else {
panic!("expected ChainError");
}
}
#[test]
fn test_router_error_display_chain() {
let err = RouterError::ChainError("chain broken".to_string());
let display = err.to_string();
assert!(display.contains("chain broken"));
}
#[test]
fn test_router_error_display_checkpoint() {
let err = RouterError::CheckpointError("cp failed".to_string());
let display = err.to_string();
assert!(display.contains("cp failed"));
}
#[test]
fn test_router_error_display_blob() {
let err = RouterError::BlobError("blob failed".to_string());
let display = err.to_string();
assert!(display.contains("blob failed"));
}
#[test]
fn test_router_error_display_vault() {
let err = RouterError::VaultError("vault failed".to_string());
let display = err.to_string();
assert!(display.contains("vault failed"));
}
#[test]
fn test_router_error_display_cache() {
let err = RouterError::CacheError("cache failed".to_string());
let display = err.to_string();
assert!(display.contains("cache failed"));
}
#[test]
fn test_router_error_display_type_mismatch() {
let err = RouterError::TypeMismatch("expected int".to_string());
let display = err.to_string();
assert!(display.contains("expected int"));
}
#[test]
fn test_router_error_display_not_found() {
let err = RouterError::NotFound("table foo".to_string());
let display = err.to_string();
assert!(display.contains("table foo"));
}
#[test]
fn test_router_error_display_missing_argument() {
let err = RouterError::MissingArgument("table name".to_string());
let display = err.to_string();
assert!(display.contains("table name"));
}
#[test]
fn test_router_error_display_auth_required() {
let err = RouterError::AuthenticationRequired;
let display = err.to_string();
assert!(display.contains("Authentication required"));
}
#[test]
fn test_router_error_display_invalid_argument() {
let err = RouterError::InvalidArgument("bad arg".to_string());
let display = err.to_string();
assert!(display.contains("bad arg"));
}
#[test]
fn test_graph_constraint_create_unique_node() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let result = router
.execute_parsed("CONSTRAINT CREATE unique_name ON NODE person PROPERTY name UNIQUE")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_constraint_create_exists_edge() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let result = router
.execute_parsed("CONSTRAINT CREATE req_weight ON EDGE knows PROPERTY weight EXISTS")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_constraint_create_type_int() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let result = router
.execute_parsed("CONSTRAINT CREATE age_type ON NODE person PROPERTY age TYPE INT")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_constraint_create_type_float() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CONSTRAINT CREATE score_type ON NODE person PROPERTY score TYPE FLOAT")
.unwrap();
}
#[test]
fn test_graph_constraint_create_type_bool() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed(
"CONSTRAINT CREATE active_type ON NODE person PROPERTY active TYPE BOOL",
)
.unwrap();
}
#[test]
fn test_graph_constraint_create_type_string() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CONSTRAINT CREATE name_type ON NODE person PROPERTY name TYPE STRING")
.unwrap();
}
#[test]
fn test_graph_constraint_list() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CONSTRAINT CREATE c1 ON NODE person PROPERTY name UNIQUE")
.unwrap();
let result = router.execute_parsed("CONSTRAINT LIST").unwrap();
let constraints = unwrap_qr_constraints(result);
assert_eq!(constraints.len(), 1);
assert_eq!(constraints[0].name, "c1");
assert!(constraints[0].target.contains("Node"));
assert_eq!(constraints[0].constraint_type, "UNIQUE");
}
#[test]
fn test_graph_constraint_get() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CONSTRAINT CREATE c1 ON NODE person PROPERTY name EXISTS")
.unwrap();
let result = router.execute_parsed("CONSTRAINT GET c1").unwrap();
let constraints = unwrap_qr_constraints(result);
assert_eq!(constraints.len(), 1);
assert_eq!(constraints[0].constraint_type, "EXISTS");
}
#[test]
fn test_graph_constraint_get_not_found() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let result = router.execute_parsed("CONSTRAINT GET nonexistent").unwrap();
let constraints = unwrap_qr_constraints(result);
assert!(constraints.is_empty());
}
#[test]
fn test_graph_constraint_drop() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CONSTRAINT CREATE c1 ON NODE person PROPERTY name UNIQUE")
.unwrap();
let result = router.execute_parsed("CONSTRAINT DROP c1").unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_constraint_on_all_nodes() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CONSTRAINT CREATE c_all ON NODE PROPERTY id UNIQUE")
.unwrap();
let result = router.execute_parsed("CONSTRAINT LIST").unwrap();
let constraints = unwrap_qr_constraints(result);
assert!(constraints[0].target.contains("AllNodes"));
}
#[test]
fn test_graph_constraint_on_all_edges() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CONSTRAINT CREATE c_all ON EDGE PROPERTY weight EXISTS")
.unwrap();
let result = router.execute_parsed("CONSTRAINT LIST").unwrap();
let constraints = unwrap_qr_constraints(result);
assert!(constraints[0].target.contains("AllEdges"));
}
#[test]
fn test_graph_aggregate_node_sum() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router.execute("NODE CREATE person { age: 30 }").unwrap();
router.execute("NODE CREATE person { age: 25 }").unwrap();
let result = router
.execute_parsed("AGGREGATE NODE PROPERTY age SUM")
.unwrap();
if let QueryResult::Aggregate(AggregateResultValue::Sum(sum)) = result {
assert!((sum - 55.0).abs() < 0.01);
} else {
panic!("expected Aggregate Sum result");
}
}
#[test]
fn test_graph_aggregate_node_avg() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router.execute("NODE CREATE person { age: 30 }").unwrap();
router.execute("NODE CREATE person { age: 20 }").unwrap();
let result = router
.execute_parsed("AGGREGATE NODE PROPERTY age AVG")
.unwrap();
if let QueryResult::Aggregate(AggregateResultValue::Avg(avg)) = result {
assert!((avg - 25.0).abs() < 0.01);
} else {
panic!("expected Aggregate Avg result");
}
}
#[test]
fn test_graph_aggregate_node_min() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router.execute("NODE CREATE person { age: 30 }").unwrap();
router.execute("NODE CREATE person { age: 20 }").unwrap();
let result = router
.execute_parsed("AGGREGATE NODE PROPERTY age MIN")
.unwrap();
if let QueryResult::Aggregate(AggregateResultValue::Min(min)) = result {
assert!((min - 20.0).abs() < 0.01);
} else {
panic!("expected Aggregate Min result");
}
}
#[test]
fn test_graph_aggregate_node_max() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router.execute("NODE CREATE person { age: 30 }").unwrap();
router.execute("NODE CREATE person { age: 20 }").unwrap();
let result = router
.execute_parsed("AGGREGATE NODE PROPERTY age MAX")
.unwrap();
if let QueryResult::Aggregate(AggregateResultValue::Max(max)) = result {
assert!((max - 30.0).abs() < 0.01);
} else {
panic!("expected Aggregate Max result");
}
}
#[test]
fn test_graph_aggregate_node_count() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router.execute("NODE CREATE person { age: 30 }").unwrap();
router.execute("NODE CREATE person { age: 20 }").unwrap();
let result = router
.execute_parsed("AGGREGATE NODE PROPERTY age COUNT")
.unwrap();
if let QueryResult::Aggregate(AggregateResultValue::Count(count)) = result {
assert_eq!(count, 2);
} else {
panic!("expected Aggregate Count result");
}
}
#[test]
fn test_graph_aggregate_node_sum_by_label() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router.execute("NODE CREATE person { age: 30 }").unwrap();
router.execute("NODE CREATE person { age: 25 }").unwrap();
let result = router
.execute_parsed("AGGREGATE NODE PROPERTY age SUM BY LABEL person")
.unwrap();
if let QueryResult::Aggregate(AggregateResultValue::Sum(sum)) = result {
assert!((sum - 55.0).abs() < 0.01);
} else {
panic!("expected Aggregate Sum result");
}
}
#[test]
fn test_graph_aggregate_edge_sum() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let n1 = match router.execute("NODE CREATE person { name: 'A' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
let n2 = match router.execute("NODE CREATE person { name: 'B' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
router
.execute(&format!(
"EDGE CREATE {n1} -> {n2} : knows {{ weight: 1.5 }}"
))
.unwrap();
let result = router
.execute_parsed("AGGREGATE EDGE PROPERTY weight SUM")
.unwrap();
assert!(matches!(result, QueryResult::Aggregate(_)));
}
#[test]
fn test_graph_aggregate_edge_sum_by_type() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let n1 = match router.execute("NODE CREATE person { name: 'A' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
let n2 = match router.execute("NODE CREATE person { name: 'B' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
router
.execute(&format!(
"EDGE CREATE {n1} -> {n2} : knows {{ weight: 1.5 }}"
))
.unwrap();
let result = router
.execute_parsed("AGGREGATE EDGE PROPERTY weight SUM BY TYPE knows")
.unwrap();
assert!(matches!(result, QueryResult::Aggregate(_)));
}
#[test]
fn test_select_with_offset_clause() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE items (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES (1, 'first')")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES (2, 'second')")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES (3, 'third')")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM items ORDER BY id LIMIT 2 OFFSET 1")
.unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_select_order_by_nulls_first() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE nfirst (id INT, val INT)")
.unwrap();
router
.execute_parsed("INSERT INTO nfirst VALUES (1, 10)")
.unwrap();
router
.execute_parsed("INSERT INTO nfirst VALUES (2, NULL)")
.unwrap();
router
.execute_parsed("INSERT INTO nfirst VALUES (3, 5)")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM nfirst ORDER BY val ASC NULLS FIRST")
.unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn test_select_order_by_nulls_last() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE nlast (id INT, val INT)")
.unwrap();
router
.execute_parsed("INSERT INTO nlast VALUES (1, 10)")
.unwrap();
router
.execute_parsed("INSERT INTO nlast VALUES (2, NULL)")
.unwrap();
router
.execute_parsed("INSERT INTO nlast VALUES (3, 5)")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM nlast ORDER BY val ASC NULLS LAST")
.unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn test_sql_count_with_column() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE ctest (id INT, val INT)")
.unwrap();
router
.execute_parsed("INSERT INTO ctest VALUES (1, 10)")
.unwrap();
router
.execute_parsed("INSERT INTO ctest VALUES (2, NULL)")
.unwrap();
let result = router
.execute_parsed("SELECT COUNT(val) FROM ctest")
.unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
}
#[test]
fn test_sql_sum_with_floats() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE ftest (id INT, val FLOAT)")
.unwrap();
router
.execute_parsed("INSERT INTO ftest VALUES (1, 1.5)")
.unwrap();
router
.execute_parsed("INSERT INTO ftest VALUES (2, 2.5)")
.unwrap();
let result = router.execute_parsed("SELECT SUM(val) FROM ftest").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn test_sql_avg_with_floats() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE favg (id INT, val FLOAT)")
.unwrap();
router
.execute_parsed("INSERT INTO favg VALUES (1, 10.0)")
.unwrap();
router
.execute_parsed("INSERT INTO favg VALUES (2, 20.0)")
.unwrap();
let result = router.execute_parsed("SELECT AVG(val) FROM favg").unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn test_sql_min_max_with_strings() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE stest (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO stest VALUES (1, 'alpha')")
.unwrap();
router
.execute_parsed("INSERT INTO stest VALUES (2, 'beta')")
.unwrap();
let min_result = router
.execute_parsed("SELECT MIN(name) FROM stest")
.unwrap();
assert!(matches!(min_result, QueryResult::Rows(_)));
let max_result = router
.execute_parsed("SELECT MAX(name) FROM stest")
.unwrap();
assert!(matches!(max_result, QueryResult::Rows(_)));
}
#[test]
fn test_group_by_with_bool() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE gtest (id INT, flag BOOLEAN, val INT)")
.unwrap();
router
.execute_parsed("INSERT INTO gtest VALUES (1, true, 10)")
.unwrap();
router
.execute_parsed("INSERT INTO gtest VALUES (2, false, 20)")
.unwrap();
router
.execute_parsed("INSERT INTO gtest VALUES (3, true, 30)")
.unwrap();
let result = router
.execute_parsed("SELECT flag, SUM(val) FROM gtest GROUP BY flag")
.unwrap();
assert!(matches!(result, QueryResult::Rows(_)));
}
#[test]
fn test_describe_node_label() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute("NODE CREATE person { name: 'Alice' }")
.unwrap();
let result = router.execute_parsed("DESCRIBE NODE person").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn test_describe_edge_type() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
let n1 = match router
.execute("NODE CREATE person { name: 'Alice' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
let n2 = match router
.execute("NODE CREATE person { name: 'Bob' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("expected Ids, got {other:?}"),
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : knows"))
.unwrap();
let result = router.execute_parsed("DESCRIBE EDGE knows").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn test_insert_select() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE src_tbl (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("CREATE TABLE dst_tbl (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO src_tbl VALUES (1, 'alice')")
.unwrap();
router
.execute_parsed("INSERT INTO src_tbl VALUES (2, 'bob')")
.unwrap();
router
.execute_parsed("INSERT INTO dst_tbl SELECT * FROM src_tbl")
.unwrap();
let result = router.execute_parsed("SELECT * FROM dst_tbl").unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_qualified_column_in_select() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE qtbl (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO qtbl VALUES (1, 'alice')")
.unwrap();
let result = router.execute_parsed("SELECT qtbl.name FROM qtbl").unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
}
#[test]
fn test_describe_table() {
let mut router = QueryRouter::new();
router.set_identity("user:test");
router
.execute_parsed("CREATE TABLE desc_tbl (id INT, name TEXT)")
.unwrap();
let result = router.execute_parsed("DESCRIBE TABLE desc_tbl").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn test_property_to_string_datetime() {
let router = QueryRouter::new();
router
.graph
.create_node("ts_label", {
let mut props = HashMap::new();
props.insert(
"created".to_string(),
PropertyValue::DateTime(1_700_000_000),
);
props
})
.unwrap();
let result = router.execute_parsed("NODE LIST").unwrap();
assert!(matches!(result, QueryResult::Nodes(_)));
}
#[test]
fn test_property_to_string_list() {
let router = QueryRouter::new();
router
.graph
.create_node("list_label", {
let mut props = HashMap::new();
props.insert(
"tags".to_string(),
PropertyValue::List(vec![
PropertyValue::String("a".to_string()),
PropertyValue::String("b".to_string()),
]),
);
props
})
.unwrap();
let result = router.execute_parsed("NODE LIST").unwrap();
assert!(matches!(result, QueryResult::Nodes(_)));
}
#[test]
fn test_property_to_string_map() {
let router = QueryRouter::new();
router
.graph
.create_node("map_label", {
let mut props = HashMap::new();
let mut inner = HashMap::new();
inner.insert("x".to_string(), PropertyValue::Int(1));
props.insert("meta".to_string(), PropertyValue::Map(inner));
props
})
.unwrap();
let result = router.execute_parsed("NODE LIST").unwrap();
assert!(matches!(result, QueryResult::Nodes(_)));
}
#[test]
fn test_property_to_string_bytes() {
let router = QueryRouter::new();
router
.graph
.create_node("bytes_label", {
let mut props = HashMap::new();
props.insert("data".to_string(), PropertyValue::Bytes(vec![1, 2, 3]));
props
})
.unwrap();
let result = router.execute_parsed("NODE LIST").unwrap();
assert!(matches!(result, QueryResult::Nodes(_)));
}
#[test]
fn test_property_to_string_point() {
let router = QueryRouter::new();
router
.graph
.create_node("point_label", {
let mut props = HashMap::new();
props.insert(
"location".to_string(),
PropertyValue::Point {
lat: 40.7128,
lon: -74.006,
},
);
props
})
.unwrap();
let result = router.execute_parsed("NODE LIST").unwrap();
assert!(matches!(result, QueryResult::Nodes(_)));
}
#[test]
fn test_select_offset_within_range() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE off_tbl (id INT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO off_tbl VALUES (1, 'a')")
.unwrap();
router
.execute_parsed("INSERT INTO off_tbl VALUES (2, 'b')")
.unwrap();
router
.execute_parsed("INSERT INTO off_tbl VALUES (3, 'c')")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM off_tbl OFFSET 1")
.unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_select_offset_exceeds_rows() {
let router = QueryRouter::new();
router.execute_parsed("CREATE TABLE off2 (id INT)").unwrap();
router
.execute_parsed("INSERT INTO off2 VALUES (1)")
.unwrap();
let result = router
.execute_parsed("SELECT * FROM off2 OFFSET 100")
.unwrap();
let rows = unwrap_qr_rows(result);
assert!(rows.is_empty());
}
#[test]
fn test_unified_result_from_conversion() {
use tensor_unified::UnifiedResult as TensorUnifiedResult;
let tensor_result = TensorUnifiedResult {
description: "test desc".to_string(),
items: vec![],
};
let result: UnifiedResult = tensor_result.into();
assert_eq!(result.description, "test desc");
assert!(result.items.is_empty());
}
#[test]
fn test_graph_aggregate_count_all_edges() {
let router = QueryRouter::new();
let n1 = if let QueryResult::Ids(ids) =
router.execute("NODE CREATE person { name: 'A' }").unwrap()
{
ids[0]
} else {
panic!("expected Ids");
};
let n2 = if let QueryResult::Ids(ids) =
router.execute("NODE CREATE person { name: 'B' }").unwrap()
{
ids[0]
} else {
panic!("expected Ids");
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : knows {{ weight: 5 }}"))
.unwrap();
let result = router
.execute_parsed("AGGREGATE EDGE PROPERTY weight SUM")
.unwrap();
assert!(matches!(result, QueryResult::Aggregate(_)));
}
#[test]
fn test_graph_aggregate_edge_by_type() {
let router = QueryRouter::new();
let n1 = if let QueryResult::Ids(ids) =
router.execute("NODE CREATE person { name: 'X' }").unwrap()
{
ids[0]
} else {
panic!("expected Ids");
};
let n2 = if let QueryResult::Ids(ids) =
router.execute("NODE CREATE person { name: 'Y' }").unwrap()
{
ids[0]
} else {
panic!("expected Ids");
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : likes {{ score: 3 }}"))
.unwrap();
let result = router
.execute_parsed("AGGREGATE EDGE PROPERTY score AVG BY TYPE likes")
.unwrap();
assert!(matches!(result, QueryResult::Aggregate(_)));
}
#[test]
fn test_graph_index_create_and_show_cov() {
let router = QueryRouter::new();
router
.execute("GRAPH INDEX CREATE ON NODE PROPERTY name")
.unwrap();
let result = router.execute("GRAPH INDEX SHOW ON NODE").unwrap();
assert!(matches!(result, QueryResult::GraphIndexes(_)));
}
#[test]
fn test_graph_index_drop_cov() {
let router = QueryRouter::new();
router
.execute("GRAPH INDEX CREATE ON NODE PROPERTY email")
.unwrap();
let result = router
.execute("GRAPH INDEX DROP ON NODE PROPERTY email")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_select_group_by_with_having_count() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE grp_hv (name TEXT, dept TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO grp_hv VALUES ('a', 'eng')")
.unwrap();
router
.execute_parsed("INSERT INTO grp_hv VALUES ('b', 'eng')")
.unwrap();
router
.execute_parsed("INSERT INTO grp_hv VALUES ('c', 'sales')")
.unwrap();
let result = router
.execute_parsed("SELECT dept, COUNT(*) FROM grp_hv GROUP BY dept")
.unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_insert_select_statement() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE src_t (id INT, val TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO src_t VALUES (1, 'hello')")
.unwrap();
router
.execute_parsed("CREATE TABLE dst_t (id INT, val TEXT)")
.unwrap();
let result = router
.execute_parsed("INSERT INTO dst_t SELECT * FROM src_t")
.unwrap();
assert!(matches!(result, QueryResult::Ids(ref ids) if !ids.is_empty()));
}
#[test]
fn test_sql_decimal_and_varchar_types() {
let router = QueryRouter::new();
let result = router
.execute_parsed("CREATE TABLE typed (amount DECIMAL(10,2), name VARCHAR(50))")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_case_expression_in_select() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE case_t (val INT)")
.unwrap();
router
.execute_parsed("INSERT INTO case_t VALUES (5)")
.unwrap();
router
.execute_parsed("INSERT INTO case_t VALUES (0)")
.unwrap();
let result = router
.execute_parsed("SELECT CASE WHEN val > 0 THEN 'positive' ELSE 'zero' END FROM case_t")
.unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
#[should_panic(expected = "expected ArtifactInfo")]
fn test_unwrap_qr_artifactinfo_wrong_variant() {
unwrap_qr_artifactinfo(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected ArtifactList")]
fn test_unwrap_qr_artifactlist_wrong_variant() {
unwrap_qr_artifactlist(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Blob")]
fn test_unwrap_qr_blob_wrong_variant() {
unwrap_qr_blob(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected BlobStats")]
fn test_unwrap_qr_blobstats_wrong_variant() {
unwrap_qr_blobstats(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected CheckpointList")]
fn test_unwrap_qr_checkpointlist_wrong_variant() {
unwrap_qr_checkpointlist(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Constraints")]
fn test_unwrap_qr_constraints_wrong_variant() {
unwrap_qr_constraints(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Edges")]
fn test_unwrap_qr_edges_wrong_variant() {
unwrap_qr_edges(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Nodes")]
fn test_unwrap_qr_nodes_wrong_variant() {
unwrap_qr_nodes(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Rows")]
fn test_unwrap_qr_rows_wrong_variant() {
unwrap_qr_rows(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Similar")]
fn test_unwrap_qr_similar_wrong_variant() {
unwrap_qr_similar(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Unified")]
fn test_unwrap_qr_unified_wrong_variant() {
unwrap_qr_unified(QueryResult::Empty);
}
#[test]
#[should_panic(expected = "expected Value")]
fn test_unwrap_qr_value_wrong_variant() {
unwrap_qr_value(QueryResult::Empty);
}
#[test]
fn test_avg_float_column() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT AVG(price) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let avg = rows[0]
.values
.iter()
.find(|(k, _)| k == "AVG(price)")
.unwrap()
.1
.clone();
assert!(matches!(avg, Value::Float(f) if (f - 1.4375).abs() < 0.001));
}
#[test]
fn test_min_float_column() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT MIN(price) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let min = rows[0]
.values
.iter()
.find(|(k, _)| k == "MIN(price)")
.unwrap()
.1
.clone();
assert_eq!(min, Value::Float(0.75));
}
#[test]
fn test_max_float_column() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT MAX(price) FROM sales").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1);
let max = rows[0]
.values
.iter()
.find(|(k, _)| k == "MAX(price)")
.unwrap()
.1
.clone();
assert_eq!(max, Value::Float(2.0));
}
#[test]
fn test_select_offset() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT * FROM sales OFFSET 2").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2); }
#[test]
fn test_select_offset_past_end_clears() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT * FROM sales OFFSET 100").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert!(rows.is_empty()); }
#[test]
fn test_where_and_condition() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt =
parser::parse("SELECT * FROM sales WHERE amount > 5 AND product = 'Apple'").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1); }
#[test]
fn test_where_or_condition() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt =
parser::parse("SELECT * FROM sales WHERE product = 'Apple' OR product = 'Cherry'")
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 3); }
#[test]
fn test_graph_index_create_edge_property() {
let router = QueryRouter::new();
router.execute("NODE CREATE person { name: 'A' }").unwrap();
router.execute("NODE CREATE person { name: 'B' }").unwrap();
let result = router
.execute("GRAPH INDEX CREATE ON EDGE PROPERTY weight")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_index_create_on_label() {
let router = QueryRouter::new();
router.execute("NODE CREATE person { name: 'A' }").unwrap();
let result = router.execute("GRAPH INDEX CREATE ON LABEL");
assert!(result.is_ok() || format!("{result:?}").contains("already exists"));
}
#[test]
fn test_graph_index_create_on_edge_type() {
let router = QueryRouter::new();
let result = router.execute("GRAPH INDEX CREATE ON EDGE TYPE").unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_index_drop_node_property() {
let router = QueryRouter::new();
router
.execute("GRAPH INDEX CREATE ON NODE PROPERTY name")
.unwrap();
let result = router
.execute("GRAPH INDEX DROP ON NODE PROPERTY name")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_index_drop_edge_property() {
let router = QueryRouter::new();
router
.execute("GRAPH INDEX CREATE ON EDGE PROPERTY weight")
.unwrap();
let result = router
.execute("GRAPH INDEX DROP ON EDGE PROPERTY weight")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_index_show_on_edge() {
let router = QueryRouter::new();
let result = router.execute("GRAPH INDEX SHOW ON EDGE").unwrap();
assert!(matches!(result, QueryResult::GraphIndexes(_)));
}
#[test]
fn test_graph_aggregate_node_property_by_label() {
let router = QueryRouter::new();
router
.execute("NODE CREATE person { name: 'A', age: 25 }")
.unwrap();
router
.execute("NODE CREATE person { name: 'B', age: 35 }")
.unwrap();
let result = router
.execute_parsed("AGGREGATE NODE PROPERTY age SUM BY LABEL person")
.unwrap();
assert!(matches!(result, QueryResult::Aggregate(_)));
}
#[test]
fn test_graph_aggregate_edge_property_sum() {
let router = QueryRouter::new();
let n1 = if let QueryResult::Ids(ids) =
router.execute("NODE CREATE person { name: 'X' }").unwrap()
{
ids[0]
} else {
panic!("expected Ids");
};
let n2 = if let QueryResult::Ids(ids) =
router.execute("NODE CREATE person { name: 'Y' }").unwrap()
{
ids[0]
} else {
panic!("expected Ids");
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : knows {{ weight: 5 }}"))
.unwrap();
let result = router
.execute_parsed("AGGREGATE EDGE PROPERTY weight SUM")
.unwrap();
assert!(matches!(result, QueryResult::Aggregate(_)));
}
#[test]
fn test_entity_create_and_get_cov() {
let router = QueryRouter::new();
let result = router
.execute_parsed("ENTITY CREATE 'test_ent' { name: 'Alice', role: 'admin' }")
.unwrap();
assert!(matches!(result, QueryResult::Value(_)));
let result = router.execute_parsed("ENTITY GET 'test_ent'").unwrap();
assert!(matches!(result, QueryResult::Unified(_)));
}
#[test]
fn test_entity_delete_cov() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'del_ent' { name: 'Bob' }")
.unwrap();
let result = router.execute_parsed("ENTITY DELETE 'del_ent'").unwrap();
assert!(matches!(result, QueryResult::Value(_) | QueryResult::Empty));
}
#[test]
fn test_similar_in_collection() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'c1' [1.0, 0.0] COLLECTION 'grp'")
.unwrap();
router
.execute_parsed("EMBED STORE 'c2' [0.9, 0.1] COLLECTION 'grp'")
.unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0] TOP 2 COLLECTION 'grp'")
.unwrap();
assert!(matches!(result, QueryResult::Similar(ref s) if !s.is_empty()));
}
fn setup_float_group_table(router: &QueryRouter) {
router
.execute_parsed("CREATE TABLE items (category TEXT, price FLOAT, name TEXT)")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES ('fruit', 1.50, 'apple')")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES ('fruit', 0.75, 'banana')")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES ('fruit', 2.00, 'cherry')")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES ('veggie', 3.00, 'carrot')")
.unwrap();
router
.execute_parsed("INSERT INTO items VALUES ('veggie', 1.25, 'peas')")
.unwrap();
}
#[test]
fn test_group_by_min_float() {
let router = QueryRouter::new();
setup_float_group_table(&router);
let stmt =
parser::parse("SELECT category, MIN(price) FROM items GROUP BY category").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_group_by_max_float() {
let router = QueryRouter::new();
setup_float_group_table(&router);
let stmt =
parser::parse("SELECT category, MAX(price) FROM items GROUP BY category").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_group_by_avg_float() {
let router = QueryRouter::new();
setup_float_group_table(&router);
let stmt =
parser::parse("SELECT category, AVG(price) FROM items GROUP BY category").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_group_by_min_string() {
let router = QueryRouter::new();
setup_float_group_table(&router);
let stmt =
parser::parse("SELECT category, MIN(name) FROM items GROUP BY category").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_group_by_max_string() {
let router = QueryRouter::new();
setup_float_group_table(&router);
let stmt =
parser::parse("SELECT category, MAX(name) FROM items GROUP BY category").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_group_by_count_column() {
let router = QueryRouter::new();
setup_float_group_table(&router);
let stmt =
parser::parse("SELECT category, COUNT(name) FROM items GROUP BY category").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_cypher_match_basic() {
let router = QueryRouter::new();
router
.execute("NODE CREATE person { name: 'Alice' }")
.unwrap();
let result = router.execute_parsed("MATCH (n:person) RETURN n");
let _ = result;
}
#[test]
fn test_cypher_create_basic() {
let router = QueryRouter::new();
let result = router.execute_parsed("CREATE (n:person {name: 'Bob'})");
let _ = result;
}
#[test]
fn test_where_lt_comparison() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT * FROM sales WHERE amount < 15").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2); }
#[test]
fn test_where_le_comparison() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT * FROM sales WHERE amount <= 10").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2); }
#[test]
fn test_paginated_select_query() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let opts = PaginationOptions {
page_size: Some(2),
cursor: None,
cursor_ttl: None,
count_total: true,
};
let result = router
.execute_paginated("SELECT * FROM sales", opts)
.unwrap();
assert!(result.total_count.is_some());
}
#[test]
fn test_select_limit_and_offset() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT * FROM sales LIMIT 2 OFFSET 1").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_entity_update_cov() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'upd_ent' { name: 'Old' }")
.unwrap();
let result = router.execute_parsed("ENTITY UPDATE 'upd_ent' { name: 'New' }");
let _ = result;
}
#[test]
fn test_entity_connect_cov() {
let router = QueryRouter::new();
router
.execute_parsed("ENTITY CREATE 'e1' { name: 'Alice' }")
.unwrap();
router
.execute_parsed("ENTITY CREATE 'e2' { name: 'Bob' }")
.unwrap();
let result = router.execute_parsed("ENTITY CONNECT 'e1' TO 'e2' AS 'knows'");
let _ = result;
}
#[test]
fn test_chain_height_no_init() {
let router = QueryRouter::new();
let result = router.execute_parsed("CHAIN HEIGHT");
assert!(result.is_err());
}
#[test]
fn test_chain_tip_no_init() {
let router = QueryRouter::new();
let result = router.execute_parsed("CHAIN TIP");
assert!(result.is_err());
}
#[test]
fn test_cluster_status_not_connected() {
let router = QueryRouter::new();
let result = router.execute_parsed("CLUSTER STATUS").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn test_cluster_disconnect_not_connected() {
let router = QueryRouter::new();
let result = router.execute_parsed("CLUSTER DISCONNECT");
let _ = result;
}
#[test]
fn test_group_by_having_sum() {
let router = QueryRouter::new();
setup_float_group_table(&router);
let stmt = parser::parse(
"SELECT category, SUM(price) FROM items GROUP BY category HAVING SUM(price) > 3.0",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2);
}
#[test]
fn test_show_tables_with_data() {
let router = QueryRouter::new();
router
.execute_parsed("CREATE TABLE show_t1 (id INT)")
.unwrap();
router
.execute_parsed("CREATE TABLE show_t2 (name TEXT)")
.unwrap();
let result = router.execute_parsed("SHOW TABLES").unwrap();
assert!(matches!(result, QueryResult::TableList(_)));
}
#[test]
fn test_select_order_by_desc() {
let router = QueryRouter::new();
setup_aggregate_table(&router);
let stmt = parser::parse("SELECT * FROM sales ORDER BY amount DESC").unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
let first_amount = rows[0]
.values
.iter()
.find(|(k, _)| k == "amount")
.unwrap()
.1
.clone();
assert_eq!(first_amount, Value::Int(20));
}
#[test]
fn test_join_with_offset_cov() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt =
parser::parse("SELECT * FROM orders JOIN users ON orders.user_id = users.id OFFSET 1")
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2); }
#[test]
fn test_join_with_offset_exceeds_cov() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM orders JOIN users ON orders.user_id = users.id OFFSET 100",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert!(rows.is_empty());
}
#[test]
fn test_join_with_where_lt_cov() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE amount < 150",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1); }
#[test]
fn test_join_with_where_ne_cov() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE name != 'Alice'",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1); }
fn blob_put(router: &QueryRouter, name: &str, data: &str) -> String {
let result = router
.execute_parsed(&format!("BLOB PUT '{name}' '{data}'"))
.unwrap();
unwrap_qr_value(result)
}
#[test]
fn test_blob_info_and_stats_cov() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let id = blob_put(&router, "testblob", "hello world");
let info = router.execute_parsed(&format!("BLOB INFO '{id}'")).unwrap();
assert!(matches!(info, QueryResult::ArtifactInfo(_)));
let stats = router.execute_parsed("BLOB STATS").unwrap();
assert!(matches!(stats, QueryResult::BlobStats(_)));
}
#[test]
fn test_blob_tag_and_untag_cov() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let id = blob_put(&router, "tagged_blob2", "data");
let result = router
.execute_parsed(&format!("BLOB TAG '{id}' 'important'"))
.unwrap();
assert!(matches!(result, QueryResult::Empty));
let result = router
.execute_parsed(&format!("BLOB UNTAG '{id}' 'important'"))
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_blob_link_and_unlink_cov() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let id = blob_put(&router, "linked2", "data");
let result = router
.execute_parsed(&format!("BLOB LINK '{id}' TO 'entity:1'"))
.unwrap();
assert!(matches!(result, QueryResult::Empty));
let result = router
.execute_parsed(&format!("BLOB UNLINK '{id}' FROM 'entity:1'"))
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_blob_verify_cov() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let id = blob_put(&router, "verified2", "data");
let result = router.execute_parsed(&format!("BLOB VERIFY '{id}'"));
let _ = result;
}
#[test]
fn test_blob_meta_set_get_cov() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
let id = blob_put(&router, "metablob2", "data");
let result = router.execute_parsed(&format!("BLOB META SET '{id}' 'key' 'value'"));
let _ = result;
let result = router.execute_parsed(&format!("BLOB META GET '{id}' 'key'"));
let _ = result;
}
#[test]
fn test_blobs_all_and_by_type_cov() {
let mut router = QueryRouter::new();
router.init_blob().unwrap();
router.set_identity("user:test");
blob_put(&router, "a.txt", "data1");
blob_put(&router, "b.txt", "data2");
let result = router.execute_parsed("BLOBS").unwrap();
assert!(matches!(result, QueryResult::ArtifactList(_)));
let result = router.execute_parsed("BLOBS WHERE TYPE 'text/plain'");
let _ = result;
}
#[test]
fn test_similar_with_collection_and_filter() {
let router = QueryRouter::new();
router
.execute_parsed("EMBED STORE 'f1' [1.0, 0.0] COLLECTION 'filtered'")
.unwrap();
router
.execute_parsed("EMBED STORE 'f2' [0.9, 0.1] COLLECTION 'filtered'")
.unwrap();
let result = router
.execute_parsed("SIMILAR [1.0, 0.0] TOP 5 COLLECTION 'filtered' WHERE key = 'f1'");
let _ = result;
}
#[test]
fn test_checkpoint_and_rollback() {
let dir = tempfile::tempdir().unwrap();
let mut router = QueryRouter::new();
router.set_checkpoint_dir(dir.path().to_path_buf());
router.init_checkpoint().unwrap();
router
.execute_parsed("CREATE TABLE ckpt_t (id INT)")
.unwrap();
router
.execute_parsed("INSERT INTO ckpt_t VALUES (1)")
.unwrap();
let result = router.execute_parsed("CHECKPOINT").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
let result = router.execute_parsed("CHECKPOINTS").unwrap();
assert!(matches!(result, QueryResult::CheckpointList(_)));
}
#[test]
fn test_node_get_datetime_property() {
let router = QueryRouter::new();
let id = router
.graph
.create_node("ts_node", {
let mut props = HashMap::new();
props.insert(
"created".to_string(),
PropertyValue::DateTime(1_700_000_000),
);
props
})
.unwrap();
let result = router.execute_parsed(&format!("NODE GET {id}")).unwrap();
let nodes = unwrap_qr_nodes(result);
assert!(nodes[0].properties.get("created").is_some());
}
#[test]
fn test_node_get_list_property() {
let router = QueryRouter::new();
let id = router
.graph
.create_node("list_node", {
let mut props = HashMap::new();
props.insert(
"tags".to_string(),
PropertyValue::List(vec![
PropertyValue::String("a".to_string()),
PropertyValue::String("b".to_string()),
]),
);
props
})
.unwrap();
let result = router.execute_parsed(&format!("NODE GET {id}")).unwrap();
let nodes = unwrap_qr_nodes(result);
assert!(nodes[0].properties.get("tags").unwrap().contains("["));
}
#[test]
fn test_node_get_map_property() {
let router = QueryRouter::new();
let id = router
.graph
.create_node("map_node", {
let mut props = HashMap::new();
let mut inner = HashMap::new();
inner.insert("x".to_string(), PropertyValue::Int(1));
props.insert("meta".to_string(), PropertyValue::Map(inner));
props
})
.unwrap();
let result = router.execute_parsed(&format!("NODE GET {id}")).unwrap();
let nodes = unwrap_qr_nodes(result);
assert!(nodes[0].properties.get("meta").unwrap().contains("{"));
}
#[test]
fn test_node_get_bytes_property() {
let router = QueryRouter::new();
let id = router
.graph
.create_node("bytes_node", {
let mut props = HashMap::new();
props.insert("data".to_string(), PropertyValue::Bytes(vec![1, 2, 3]));
props
})
.unwrap();
let result = router.execute_parsed(&format!("NODE GET {id}")).unwrap();
let nodes = unwrap_qr_nodes(result);
assert!(nodes[0].properties.get("data").unwrap().contains("bytes"));
}
#[test]
fn test_node_get_point_property() {
let router = QueryRouter::new();
let id = router
.graph
.create_node("point_node", {
let mut props = HashMap::new();
props.insert(
"location".to_string(),
PropertyValue::Point {
lat: 40.7128,
lon: -74.006,
},
);
props
})
.unwrap();
let result = router.execute_parsed(&format!("NODE GET {id}")).unwrap();
let nodes = unwrap_qr_nodes(result);
assert!(nodes[0]
.properties
.get("location")
.unwrap()
.contains("POINT"));
}
#[test]
fn test_join_where_ge_cov() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE amount >= 150",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 2); }
#[test]
fn test_join_where_le_cov() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE amount <= 100",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1); }
#[test]
fn test_join_where_gt_cov() {
let router = QueryRouter::new();
setup_join_tables(&router);
let stmt = parser::parse(
"SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE amount > 150",
)
.unwrap();
let result = router.execute_statement(&stmt).unwrap();
let rows = unwrap_qr_rows(result);
assert_eq!(rows.len(), 1); }
#[test]
fn test_cypher_delete_basic() {
let router = QueryRouter::new();
router
.execute("NODE CREATE person { name: 'ToDelete' }")
.unwrap();
let result = router.execute_parsed("DELETE (n:person)");
let _ = result;
}
#[test]
fn test_cypher_merge_basic() {
let router = QueryRouter::new();
let result = router.execute_parsed("MERGE (n:person {name: 'Charlie'})");
let _ = result;
}
#[test]
fn test_spatial_insert_and_within_radius() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'a' BOUNDS 0.0 0.0 1.0 1.0")
.unwrap();
router
.execute("SPATIAL INSERT 'b' BOUNDS 5.0 5.0 1.0 1.0")
.unwrap();
router
.execute("SPATIAL INSERT 'c' BOUNDS 100.0 100.0 1.0 1.0")
.unwrap();
let result = router
.execute("SPATIAL WITHIN 3.0 3.0 RADIUS 10.0")
.unwrap();
match result {
QueryResult::Spatial(items) => {
assert_eq!(items.len(), 2);
let keys: Vec<&str> = items.iter().map(|r| r.key.as_str()).collect();
assert!(keys.contains(&"a"));
assert!(keys.contains(&"b"));
for item in &items {
assert!(item.distance >= 0.0);
}
},
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_spatial_within_radius_no_results() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'far' BOUNDS 100.0 100.0 1.0 1.0")
.unwrap();
let result = router.execute("SPATIAL WITHIN 0.0 0.0 RADIUS 1.0").unwrap();
match result {
QueryResult::Spatial(items) => assert!(items.is_empty()),
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_spatial_within_radius_with_limit() {
let router = QueryRouter::new();
for i in 0..10 {
let x = f64::from(i);
router
.execute(&format!("SPATIAL INSERT 'item{i}' BOUNDS {x} 0.0 1.0 1.0"))
.unwrap();
}
let result = router
.execute("SPATIAL WITHIN 5.0 0.0 RADIUS 100.0 LIMIT 3")
.unwrap();
match result {
QueryResult::Spatial(items) => assert_eq!(items.len(), 3),
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_spatial_delete() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'del_me' BOUNDS 1.0 2.0 3.0 4.0")
.unwrap();
let result = router.execute("SPATIAL COUNT").unwrap();
assert!(matches!(result, QueryResult::Count(1)));
router
.execute("SPATIAL DELETE 'del_me' BOUNDS 1.0 2.0 3.0 4.0")
.unwrap();
let result = router.execute("SPATIAL COUNT").unwrap();
assert!(matches!(result, QueryResult::Count(0)));
}
#[test]
fn test_spatial_count() {
let router = QueryRouter::new();
let result = router.execute("SPATIAL COUNT").unwrap();
assert!(matches!(result, QueryResult::Count(0)));
router
.execute("SPATIAL INSERT 'x' BOUNDS 0.0 0.0 1.0 1.0")
.unwrap();
router
.execute("SPATIAL INSERT 'y' BOUNDS 5.0 5.0 1.0 1.0")
.unwrap();
let result = router.execute("SPATIAL COUNT").unwrap();
assert!(matches!(result, QueryResult::Count(2)));
}
#[test]
fn test_spatial_invalid_radius() {
let router = QueryRouter::new();
let result = router.execute("SPATIAL WITHIN 0.0 0.0 RADIUS -1.0");
assert!(result.is_err());
}
#[test]
fn test_spatial_invalid_bounds() {
let router = QueryRouter::new();
let result = router.execute("SPATIAL INSERT 'bad' BOUNDS 0.0 0.0 -1.0 1.0");
assert!(result.is_err());
}
#[test]
fn test_spatial_zero_radius() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'origin' BOUNDS 0.0 0.0 1.0 1.0")
.unwrap();
let result = router.execute("SPATIAL WITHIN 0.5 0.5 RADIUS 0.0").unwrap();
match result {
QueryResult::Spatial(items) => {
assert_eq!(items.len(), 1);
assert_eq!(items[0].key, "origin");
},
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_spatial_delete_nonexistent() {
let router = QueryRouter::new();
let result = router.execute("SPATIAL DELETE 'none' BOUNDS 0.0 0.0 1.0 1.0");
assert!(result.is_err());
}
#[test]
fn test_spatial_end_to_end_parsed() {
let router = QueryRouter::new();
router
.execute_parsed("SPATIAL INSERT 'pt1' BOUNDS 1.0 1.0 2.0 2.0")
.unwrap();
router
.execute_parsed("SPATIAL INSERT 'pt2' BOUNDS 3.0 3.0 1.0 1.0")
.unwrap();
let result = router.execute_parsed("SPATIAL COUNT").unwrap();
assert!(matches!(result, QueryResult::Count(2)));
let result = router
.execute_parsed("SPATIAL WITHIN 2.0 2.0 RADIUS 5.0")
.unwrap();
match result {
QueryResult::Spatial(items) => assert_eq!(items.len(), 2),
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_spatial_nearest_basic() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'a' BOUNDS 10.0 10.0 5.0 5.0")
.unwrap();
router
.execute("SPATIAL INSERT 'b' BOUNDS 100.0 100.0 5.0 5.0")
.unwrap();
let result = router.execute("SPATIAL NEAREST 12 12").unwrap();
match result {
QueryResult::Spatial(items) => {
assert_eq!(items.len(), 1);
assert_eq!(items[0].key, "a");
},
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_spatial_nearest_with_limit() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'p1' BOUNDS 0.0 0.0 2.0 2.0")
.unwrap();
router
.execute("SPATIAL INSERT 'p2' BOUNDS 10.0 10.0 2.0 2.0")
.unwrap();
router
.execute("SPATIAL INSERT 'p3' BOUNDS 20.0 20.0 2.0 2.0")
.unwrap();
let result = router.execute("SPATIAL NEAREST 0.0 0.0 LIMIT 2").unwrap();
match result {
QueryResult::Spatial(items) => {
assert_eq!(items.len(), 2);
assert_eq!(items[0].key, "p1");
assert_eq!(items[1].key, "p2");
},
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_spatial_nearest_prefers_small_centroid() {
let router = QueryRouter::new();
router
.execute("SPATIAL INSERT 'big' BOUNDS 0.0 0.0 100.0 100.0")
.unwrap();
router
.execute("SPATIAL INSERT 'small' BOUNDS 4.0 4.0 4.0 4.0")
.unwrap();
let result = router.execute("SPATIAL NEAREST 5 5 LIMIT 2").unwrap();
match result {
QueryResult::Spatial(items) => {
assert_eq!(items.len(), 2);
assert_eq!(items[0].key, "small");
assert_eq!(items[1].key, "big");
},
other => panic!("Expected Spatial result, got: {other:?}"),
}
}
#[test]
fn test_execute_parser_path_select() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE parser_sel (id INT, name TEXT)")
.unwrap();
router
.execute("INSERT INTO parser_sel (id, name) VALUES (1, 'alice')")
.unwrap();
let result = router.execute("SELECT * FROM parser_sel").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_parser_path_insert() {
let router = QueryRouter::new();
router.execute("CREATE TABLE parser_ins (id INT)").unwrap();
router
.execute("INSERT INTO parser_ins (id) VALUES (42)")
.unwrap();
let result = router.execute("SELECT * FROM parser_ins").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
},
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_parser_path_create_table() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE parser_ct (id INT, val FLOAT)")
.unwrap();
let result = router.execute("SHOW TABLES").unwrap();
match result {
QueryResult::TableList(tables) => {
assert!(tables.contains(&"parser_ct".to_string()));
},
other => panic!("Expected TableList, got: {other:?}"),
}
}
#[test]
fn test_execute_unknown_command_error() {
let router = QueryRouter::new();
let result = router.execute("FOOBAR something");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RouterError::UnknownCommand(_)),
"Expected UnknownCommand, got: {err:?}"
);
}
#[test]
fn test_execute_empty_command_error() {
let router = QueryRouter::new();
let result = router.execute("");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, RouterError::ParseError(_)),
"Expected ParseError for empty, got: {err:?}"
);
}
#[test]
fn test_execute_whitespace_only_error() {
let router = QueryRouter::new();
let result = router.execute(" \t ");
assert!(result.is_err());
}
#[test]
fn test_is_cacheable_statement_select() {
let stmt = parser::parse("SELECT * FROM t").unwrap();
assert!(QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_is_cacheable_statement_similar() {
let stmt = parser::parse("SIMILAR [1.0, 2.0, 3.0] LIMIT 5").unwrap();
assert!(QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_is_cacheable_statement_neighbors() {
let stmt = parser::parse("NEIGHBORS 1").unwrap();
assert!(QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_is_cacheable_statement_path() {
let stmt = parser::parse("PATH 1 -> 5").unwrap();
assert!(QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_is_cacheable_statement_insert_not_cacheable() {
let stmt = parser::parse("INSERT INTO t (x) VALUES (1)").unwrap();
assert!(!QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_is_cacheable_statement_create_table_not_cacheable() {
let stmt = parser::parse("CREATE TABLE t (id INT)").unwrap();
assert!(!QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_is_cacheable_statement_node_create_not_cacheable() {
let stmt = parser::parse("NODE CREATE person").unwrap();
assert!(!QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_is_cacheable_statement_embed_store_not_cacheable() {
let stmt = parser::parse("EMBED STORE 'k' [1.0, 2.0]").unwrap();
assert!(!QueryRouter::is_cacheable_statement(&stmt));
}
#[test]
fn test_execute_cache_integration_select() {
let mut router = QueryRouter::new();
router.init_cache();
router.execute("CREATE TABLE cache_hit (id INT)").unwrap();
router
.execute("INSERT INTO cache_hit (id) VALUES (1)")
.unwrap();
let r1 = router.execute("SELECT * FROM cache_hit").unwrap();
assert!(matches!(r1, QueryResult::Rows(_)));
let r2 = router.execute("SELECT * FROM cache_hit").unwrap();
assert!(matches!(r2, QueryResult::Rows(_)));
assert!(router.cache.is_some());
}
#[test]
fn test_execute_write_invalidates_cache() {
let mut router = QueryRouter::new();
router.init_cache();
router.execute("CREATE TABLE cache_inv (id INT)").unwrap();
router
.execute("INSERT INTO cache_inv (id) VALUES (1)")
.unwrap();
let _ = router.execute("SELECT * FROM cache_inv").unwrap();
router
.execute("INSERT INTO cache_inv (id) VALUES (2)")
.unwrap();
let result = router.execute("SELECT * FROM cache_inv").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_drop_table_parser_path() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE drop_ep (id INT, val TEXT)")
.unwrap();
router
.execute("INSERT INTO drop_ep (id, val) VALUES (1, 'a')")
.unwrap();
router.execute("DROP TABLE drop_ep").unwrap();
let result = router.execute("SHOW TABLES").unwrap();
match result {
QueryResult::TableList(tables) => {
assert!(
!tables.contains(&"drop_ep".to_string()),
"Table should have been dropped"
);
},
other => panic!("Expected TableList, got: {other:?}"),
}
}
#[test]
fn test_execute_drop_index_parser_path() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE drop_idx_ep (id INT, name TEXT)")
.unwrap();
router
.execute("CREATE INDEX idx_drop_ep ON drop_idx_ep (name)")
.unwrap();
router.execute("DROP INDEX ON drop_idx_ep (name)").unwrap();
let result = router.execute("DROP INDEX ON drop_idx_ep (name)");
assert!(result.is_err(), "Dropping non-existent index should fail");
}
#[test]
fn test_execute_drop_index_if_exists_parser_path() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE drop_idx_ie (id INT, name TEXT)")
.unwrap();
let result = router
.execute("DROP INDEX IF EXISTS ON drop_idx_ie (name)")
.unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_embed_store_negative_values() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'neg_vec' [-1.0, 2.5, -3.0]")
.unwrap();
let result = router.execute("EMBED GET 'neg_vec'").unwrap();
match result {
QueryResult::Value(v) => {
assert!(
v.contains("-1") || v.contains("neg_vec"),
"Expected value containing embedding info, got: {v}"
);
},
QueryResult::Similar(items) => {
assert!(!items.is_empty());
},
other => panic!("Expected Value or Similar, got: {other:?}"),
}
}
#[test]
fn test_embed_store_mixed_negative_positive() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'mixed' [-0.5, 0.0, 1.5, -2.0]")
.unwrap();
let result = router.execute("EMBED GET 'mixed'").unwrap();
assert!(
!matches!(result, QueryResult::Empty),
"Embedding should have been stored"
);
}
#[test]
fn test_embed_store_all_negative() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'all_neg' [-1.0, -2.0, -3.0]")
.unwrap();
let result = router.execute("EMBED GET 'all_neg'").unwrap();
assert!(
!matches!(result, QueryResult::Empty),
"Embedding should have been stored"
);
}
#[test]
fn test_eof_enforcement_trailing_garbage() {
let router = QueryRouter::new();
router.execute("CREATE TABLE eof_t (id INT)").unwrap();
let result = router.execute("SELECT 1 FROM eof_t SELECT");
assert!(result.is_err(), "Trailing garbage should cause an error");
}
#[test]
fn test_eof_enforcement_semicolon_ok() {
let router = QueryRouter::new();
router.execute("CREATE TABLE eof_semi (id INT)").unwrap();
let result = router.execute("SELECT * FROM eof_semi;");
assert!(
result.is_ok(),
"Trailing semicolon should be accepted, got: {:?}",
result.unwrap_err()
);
}
#[test]
fn test_eof_enforcement_trailing_keyword() {
let router = QueryRouter::new();
router.execute("CREATE TABLE eof_kw (id INT)").unwrap();
let result = router.execute("SELECT 1 DROP");
assert!(result.is_err(), "Trailing keyword should cause an error");
}
#[test]
fn test_execute_node_create_via_execute() {
let router = QueryRouter::new();
let result = router.execute("NODE CREATE person {name: 'Alice'}");
assert!(result.is_ok(), "NODE CREATE via execute() should succeed");
}
#[test]
fn test_execute_embed_store_via_execute() {
let router = QueryRouter::new();
let result = router.execute("EMBED STORE 'e1' [1.0, 2.0, 3.0]");
assert!(result.is_ok(), "EMBED STORE via execute() should succeed");
}
#[test]
fn test_execute_show_tables_via_execute() {
let router = QueryRouter::new();
let result = router.execute("SHOW TABLES").unwrap();
assert!(matches!(result, QueryResult::TableList(_)));
}
#[test]
fn test_execute_update_via_execute() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE upd_test (id INT, val TEXT)")
.unwrap();
router
.execute("INSERT INTO upd_test (id, val) VALUES (1, 'old')")
.unwrap();
let result = router.execute("UPDATE upd_test SET val = 'new' WHERE id = 1");
assert!(result.is_ok(), "UPDATE via execute() should succeed");
}
#[test]
fn test_execute_delete_via_execute() {
let router = QueryRouter::new();
router.execute("CREATE TABLE del_test (id INT)").unwrap();
router
.execute("INSERT INTO del_test (id) VALUES (1)")
.unwrap();
let result = router.execute("DELETE FROM del_test WHERE id = 1");
assert!(result.is_ok(), "DELETE via execute() should succeed");
}
#[test]
fn test_is_write_statement_drop_table() {
let stmt = parser::parse("DROP TABLE t").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_drop_index() {
let stmt = parser::parse("DROP INDEX ON t(x)").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_node_create() {
let stmt = parser::parse("NODE CREATE person").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_embed_store() {
let stmt = parser::parse("EMBED STORE 'k' [1.0, 2.0]").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_select_is_read() {
let stmt = parser::parse("SELECT * FROM t").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_neighbors_is_read() {
let stmt = parser::parse("NEIGHBORS 1").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_similar_is_read() {
let stmt = parser::parse("SIMILAR [1.0, 2.0] LIMIT 3").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_node_get_is_read() {
let stmt = parser::parse("NODE GET 1").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_embed_get_is_read() {
let stmt = parser::parse("EMBED GET 'k'").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_execute_parse_error_non_legacy_keyword() {
let router = QueryRouter::new();
let result = router.execute("SHOW BADSUBCMD");
assert!(result.is_err(), "Invalid SHOW subcommand should error");
}
#[test]
fn test_execute_unknown_single_word() {
let router = QueryRouter::new();
let result = router.execute("XYZZY");
assert!(result.is_err());
assert!(
matches!(result.unwrap_err(), RouterError::UnknownCommand(_)),
"Single unknown word should yield UnknownCommand"
);
}
#[test]
fn test_execute_drop_table_with_data() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE drop_data (id INT, name TEXT)")
.unwrap();
for i in 0..10 {
router
.execute(&format!(
"INSERT INTO drop_data (id, name) VALUES ({i}, 'row{i}')"
))
.unwrap();
}
router.execute("DROP TABLE drop_data").unwrap();
}
#[test]
fn test_execute_drop_index_named_not_supported() {
let router = QueryRouter::new();
let result = router.execute("DROP INDEX myindex");
assert!(result.is_err(), "Named DROP INDEX should fail");
}
#[test]
fn test_execute_node_get_via_parser() {
let router = QueryRouter::new();
let result = router.execute("NODE CREATE person {name: 'Bob'}").unwrap();
let node_id = match result {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let result = router.execute(&format!("NODE GET {node_id}")).unwrap();
match result {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].id, node_id);
},
other => panic!("Expected Nodes, got: {other:?}"),
}
}
#[test]
fn test_execute_node_delete_via_parser() {
let router = QueryRouter::new();
let result = router.execute("NODE CREATE person {name: 'Del'}").unwrap();
let node_id = match result {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let del_result = router.execute(&format!("NODE DELETE {node_id}")).unwrap();
assert!(matches!(del_result, QueryResult::Count(1)));
}
#[test]
fn test_execute_node_list_via_parser() {
let router = QueryRouter::new();
router
.execute("NODE CREATE animal {species: 'cat'}")
.unwrap();
router
.execute("NODE CREATE animal {species: 'dog'}")
.unwrap();
let result = router.execute("NODE LIST animal").unwrap();
match result {
QueryResult::Nodes(nodes) => assert!(nodes.len() >= 2),
other => panic!("Expected Nodes, got: {other:?}"),
}
}
#[test]
fn test_execute_node_list_with_limit_via_parser() {
let router = QueryRouter::new();
router.execute("NODE CREATE vehicle {type: 'car'}").unwrap();
router
.execute("NODE CREATE vehicle {type: 'bike'}")
.unwrap();
router.execute("NODE CREATE vehicle {type: 'bus'}").unwrap();
let result = router.execute("NODE LIST vehicle LIMIT 2").unwrap();
match result {
QueryResult::Nodes(nodes) => assert!(nodes.len() <= 2),
other => panic!("Expected Nodes, got: {other:?}"),
}
}
#[test]
fn test_execute_edge_create_via_parser() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE person {name: 'A'}").unwrap();
let r2 = router.execute("NODE CREATE person {name: 'B'}").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router
.execute(&format!("EDGE CREATE {id1} -> {id2} : knows"))
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
other => panic!("Expected Ids, got: {other:?}"),
}
}
#[test]
fn test_execute_edge_get_via_parser() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE person {name: 'C'}").unwrap();
let r2 = router.execute("NODE CREATE person {name: 'D'}").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let edge_result = router
.execute(&format!("EDGE CREATE {id1} -> {id2} : likes"))
.unwrap();
let edge_id = match edge_result {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("EDGE GET {edge_id}")).unwrap();
match result {
QueryResult::Edges(edges) => {
assert_eq!(edges.len(), 1);
assert_eq!(edges[0].id, edge_id);
},
other => panic!("Expected Edges, got: {other:?}"),
}
}
#[test]
fn test_execute_edge_delete_via_parser() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE person {name: 'E'}").unwrap();
let r2 = router.execute("NODE CREATE person {name: 'F'}").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let edge_result = router
.execute(&format!("EDGE CREATE {id1} -> {id2} : dislikes"))
.unwrap();
let edge_id = match edge_result {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let result = router.execute(&format!("EDGE DELETE {edge_id}")).unwrap();
assert!(matches!(result, QueryResult::Count(1)));
}
#[test]
fn test_execute_edge_list_via_parser() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE person").unwrap();
let r2 = router.execute("NODE CREATE person").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {id1} -> {id2} : follows"))
.unwrap();
let result = router.execute("EDGE LIST").unwrap();
match result {
QueryResult::Edges(edges) => assert!(!edges.is_empty()),
other => panic!("Expected Edges, got: {other:?}"),
}
}
#[test]
fn test_execute_select_with_order_by() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE sort_test (id INT, name TEXT)")
.unwrap();
router
.execute("INSERT INTO sort_test (id, name) VALUES (3, 'charlie')")
.unwrap();
router
.execute("INSERT INTO sort_test (id, name) VALUES (1, 'alice')")
.unwrap();
router
.execute("INSERT INTO sort_test (id, name) VALUES (2, 'bob')")
.unwrap();
let result = router
.execute("SELECT * FROM sort_test ORDER BY id ASC")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 3),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_select_with_limit() {
let router = QueryRouter::new();
router.execute("CREATE TABLE limit_test (id INT)").unwrap();
for i in 0..5 {
router
.execute(&format!("INSERT INTO limit_test (id) VALUES ({i})"))
.unwrap();
}
let result = router.execute("SELECT * FROM limit_test LIMIT 2").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_select_count_aggregate() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE agg_test (id INT, val FLOAT)")
.unwrap();
router
.execute("INSERT INTO agg_test (id, val) VALUES (1, 10.0)")
.unwrap();
router
.execute("INSERT INTO agg_test (id, val) VALUES (2, 20.0)")
.unwrap();
let result = router.execute("SELECT COUNT(*) FROM agg_test").unwrap();
assert!(
!matches!(result, QueryResult::Empty),
"COUNT should return a result"
);
}
#[test]
fn test_execute_describe_table_via_execute() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE desc_exec (id INT, name TEXT, active BOOL)")
.unwrap();
let result = router.execute("DESCRIBE TABLE desc_exec").unwrap();
assert!(
!matches!(result, QueryResult::Empty),
"DESCRIBE should return column info"
);
}
#[test]
fn test_execute_show_embeddings_via_execute() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'show_e1' [1.0, 2.0]").unwrap();
let result = router.execute("SHOW EMBEDDINGS").unwrap();
match result {
QueryResult::Value(v) => {
assert!(v.contains("show_e1"), "Should list stored embedding");
},
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_execute_show_embeddings_with_limit() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'lim_e1' [1.0, 2.0]").unwrap();
router.execute("EMBED STORE 'lim_e2' [3.0, 4.0]").unwrap();
let result = router.execute("SHOW EMBEDDINGS LIMIT 1").unwrap();
assert!(matches!(result, QueryResult::Value(_)));
}
#[test]
fn test_execute_count_embeddings() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'cnt_e1' [1.0, 2.0]").unwrap();
let result = router.execute("COUNT EMBEDDINGS").unwrap();
match result {
QueryResult::Count(c) => assert!(c >= 1),
other => panic!("Expected Count, got: {other:?}"),
}
}
#[test]
fn test_execute_embed_delete_via_execute() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'del_emb' [1.0, 2.0]").unwrap();
let result = router.execute("EMBED DELETE 'del_emb'");
assert!(result.is_ok(), "EMBED DELETE should succeed");
}
#[test]
fn test_execute_neighbors_via_execute() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE person {name: 'N1'}").unwrap();
let r2 = router.execute("NODE CREATE person {name: 'N2'}").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {id1} -> {id2} : friends"))
.unwrap();
let result = router
.execute(&format!("NEIGHBORS {id1} OUTGOING"))
.unwrap();
assert!(
!matches!(result, QueryResult::Empty),
"NEIGHBORS should return results"
);
}
#[test]
fn test_execute_path_via_execute() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE loc {name: 'X'}").unwrap();
let r2 = router.execute("NODE CREATE loc {name: 'Y'}").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {id1} -> {id2} : road"))
.unwrap();
let result = router.execute(&format!("PATH {id1} -> {id2}")).unwrap();
match result {
QueryResult::Path(path) => assert!(!path.is_empty()),
other => panic!("Expected Path, got: {other:?}"),
}
}
#[test]
fn test_execute_similar_via_execute() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'sim1' [1.0, 0.0, 0.0]")
.unwrap();
router
.execute("EMBED STORE 'sim2' [0.9, 0.1, 0.0]")
.unwrap();
let result = router.execute("SIMILAR [1.0, 0.0, 0.0] LIMIT 2").unwrap();
match result {
QueryResult::Similar(items) => assert!(!items.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_is_write_statement_embed_delete() {
let stmt = parser::parse("EMBED DELETE 'k'").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_node_delete() {
let stmt = parser::parse("NODE DELETE 1").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_edge_create() {
let stmt = parser::parse("EDGE CREATE 1 -> 2 : knows").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_edge_delete() {
let stmt = parser::parse("EDGE DELETE 1").unwrap();
assert!(QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_edge_list_is_read() {
let stmt = parser::parse("EDGE LIST").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_node_list_is_read() {
let stmt = parser::parse("NODE LIST").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_path_is_read() {
let stmt = parser::parse("PATH 1 -> 2").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_show_tables_is_read() {
let stmt = parser::parse("SHOW TABLES").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_is_write_statement_describe_is_read() {
let stmt = parser::parse("DESCRIBE TABLE t").unwrap();
assert!(!QueryRouter::is_write_statement(&stmt));
}
#[test]
fn test_execute_select_with_where_and() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE where_and (id INT, a INT, b INT)")
.unwrap();
router
.execute("INSERT INTO where_and (id, a, b) VALUES (1, 10, 20)")
.unwrap();
router
.execute("INSERT INTO where_and (id, a, b) VALUES (2, 30, 40)")
.unwrap();
let result = router
.execute("SELECT * FROM where_and WHERE a = 10 AND b = 20")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_select_with_where_or() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE where_or (id INT, val INT)")
.unwrap();
router
.execute("INSERT INTO where_or (id, val) VALUES (1, 10)")
.unwrap();
router
.execute("INSERT INTO where_or (id, val) VALUES (2, 20)")
.unwrap();
router
.execute("INSERT INTO where_or (id, val) VALUES (3, 30)")
.unwrap();
let result = router
.execute("SELECT * FROM where_or WHERE val = 10 OR val = 30")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_select_with_comparison_operators() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE cmp_test (id INT, val INT)")
.unwrap();
for i in 1..=5 {
router
.execute(&format!(
"INSERT INTO cmp_test (id, val) VALUES ({i}, {})",
i * 10
))
.unwrap();
}
let result = router
.execute("SELECT * FROM cmp_test WHERE val > 30")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
let result = router
.execute("SELECT * FROM cmp_test WHERE val <= 20")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_insert_multiple_rows() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE multi_ins (id INT, name TEXT)")
.unwrap();
router
.execute("INSERT INTO multi_ins (id, name) VALUES (1, 'a'), (2, 'b'), (3, 'c')")
.unwrap();
let result = router.execute("SELECT * FROM multi_ins").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 3),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_show_vector_index() {
let router = QueryRouter::new();
let result = router.execute("SHOW VECTOR INDEX").unwrap();
match result {
QueryResult::Value(v) => assert!(v.contains("No HNSW") || v.contains("HNSW")),
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_execute_embed_integer_vector() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'int_vec' [1, 2, 3]").unwrap();
let result = router.execute("EMBED GET 'int_vec'").unwrap();
assert!(
!matches!(result, QueryResult::Empty),
"Integer vector should be stored"
);
}
#[test]
fn test_execute_select_with_offset() {
let router = QueryRouter::new();
router.execute("CREATE TABLE offset_test (id INT)").unwrap();
for i in 0..5 {
router
.execute(&format!("INSERT INTO offset_test (id) VALUES ({i})"))
.unwrap();
}
let result = router
.execute("SELECT * FROM offset_test LIMIT 2 OFFSET 2")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_eof_enforcement_garbage_after_semicolon() {
let router = QueryRouter::new();
let result = router.execute("SELECT 1; GARBAGE");
assert!(result.is_err(), "Garbage after semicolons should fail");
}
#[test]
fn test_execute_embed_store_negative_integer() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'neg_int' [-1, -2, 3]").unwrap();
let result = router.execute("EMBED GET 'neg_int'").unwrap();
assert!(
!matches!(result, QueryResult::Empty),
"Negative integer vector should be stored"
);
}
#[test]
fn test_execute_entity_create_via_execute() {
let router = QueryRouter::new();
let result = router.execute("ENTITY CREATE 'ent1' {name: 'test'}");
assert!(result.is_ok(), "ENTITY CREATE should succeed");
}
#[test]
fn test_execute_entity_get_with_embedding() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'ent_emb' [1.0, 2.0, 3.0]")
.unwrap();
let result = router.execute("ENTITY GET 'ent_emb'");
let _ = result;
}
#[test]
fn test_execute_entity_get_not_found() {
let router = QueryRouter::new();
let result = router.execute("ENTITY GET 'nonexistent_ent'");
assert!(result.is_err(), "ENTITY GET of missing entity should fail");
}
#[test]
fn test_execute_entity_delete_via_execute() {
let router = QueryRouter::new();
router.execute("ENTITY CREATE 'ent_del' {x: 'y'}").unwrap();
let result = router.execute("ENTITY DELETE 'ent_del'");
let _ = result;
}
#[test]
fn test_execute_entity_create_with_embedding() {
let router = QueryRouter::new();
let result =
router.execute("ENTITY CREATE 'ent_vec' {name: 'vec_test'} EMBEDDING [1.0, 2.0]");
assert!(
result.is_ok(),
"ENTITY CREATE with embedding should succeed"
);
}
#[test]
fn test_execute_embed_store_with_collection() {
let router = QueryRouter::new();
let result = router.execute("EMBED STORE 'coll_e1' [1.0, 2.0] INTO 'my_collection'");
assert!(result.is_ok(), "EMBED STORE with collection should succeed");
}
#[test]
fn test_execute_similar_with_limit() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'sl1' [1.0, 0.0, 0.0]").unwrap();
router.execute("EMBED STORE 'sl2' [0.0, 1.0, 0.0]").unwrap();
router.execute("EMBED STORE 'sl3' [0.0, 0.0, 1.0]").unwrap();
let result = router.execute("SIMILAR [1.0, 0.0, 0.0] LIMIT 1").unwrap();
match result {
QueryResult::Similar(items) => {
assert!(items.len() <= 1);
},
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_execute_graph_count_nodes() {
let router = QueryRouter::new();
router.execute("NODE CREATE counter").unwrap();
router.execute("NODE CREATE counter").unwrap();
let result = router.execute("GRAPH COUNT NODES");
let _ = result;
}
#[test]
fn test_execute_graph_count_edges() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE counter").unwrap();
let r2 = router.execute("NODE CREATE counter").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {id1} -> {id2} : counted"))
.unwrap();
let result = router.execute("GRAPH COUNT EDGES");
let _ = result;
}
#[test]
fn test_execute_select_join() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE j_users (id INT, name TEXT)")
.unwrap();
router
.execute("CREATE TABLE j_orders (id INT, user_id INT, amount FLOAT)")
.unwrap();
router
.execute("INSERT INTO j_users (id, name) VALUES (1, 'alice')")
.unwrap();
router
.execute("INSERT INTO j_users (id, name) VALUES (2, 'bob')")
.unwrap();
router
.execute("INSERT INTO j_orders (id, user_id, amount) VALUES (1, 1, 100.0)")
.unwrap();
router
.execute("INSERT INTO j_orders (id, user_id, amount) VALUES (2, 1, 200.0)")
.unwrap();
let result =
router.execute("SELECT * FROM j_users JOIN j_orders ON j_users.id = j_orders.user_id");
let _ = result;
}
#[test]
fn test_execute_graph_batch_create_nodes() {
let router = QueryRouter::new();
let result = router.execute("GRAPH BATCH CREATE NODES [{label: 'item'}, {label: 'item'}]");
let _ = result;
}
#[test]
fn test_execute_similar_by_key() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'simkey1' [1.0, 0.0]").unwrap();
router.execute("EMBED STORE 'simkey2' [0.9, 0.1]").unwrap();
let result = router.execute("SIMILAR 'simkey1' LIMIT 5").unwrap();
match result {
QueryResult::Similar(items) => assert!(!items.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_execute_embed_build_index() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'idx1' [1.0, 0.0, 0.0]")
.unwrap();
router
.execute("EMBED STORE 'idx2' [0.0, 1.0, 0.0]")
.unwrap();
let result = router.execute("EMBED BUILD INDEX");
let _ = result;
}
#[test]
fn test_execute_select_distinct() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE dist_test (id INT, cat TEXT)")
.unwrap();
router
.execute("INSERT INTO dist_test (id, cat) VALUES (1, 'a')")
.unwrap();
router
.execute("INSERT INTO dist_test (id, cat) VALUES (2, 'a')")
.unwrap();
router
.execute("INSERT INTO dist_test (id, cat) VALUES (3, 'b')")
.unwrap();
let result = router.execute("SELECT DISTINCT cat FROM dist_test");
let _ = result;
}
#[test]
fn test_execute_begin_commit() {
let router = QueryRouter::new();
let _ = router.execute("BEGIN");
let _ = router.execute("COMMIT");
}
#[test]
fn test_execute_entity_connect() {
let router = QueryRouter::new();
router.execute("ENTITY CREATE 'ec1' {name: 'a'}").unwrap();
router.execute("ENTITY CREATE 'ec2' {name: 'b'}").unwrap();
let result = router.execute("ENTITY CONNECT 'ec1' -> 'ec2' : related");
let _ = result;
}
#[test]
fn test_execute_node_list_all() {
let router = QueryRouter::new();
router.execute("NODE CREATE typeA").unwrap();
router.execute("NODE CREATE typeB").unwrap();
let result = router.execute("NODE LIST").unwrap();
match result {
QueryResult::Nodes(nodes) => assert!(nodes.len() >= 2),
other => panic!("Expected Nodes, got: {other:?}"),
}
}
#[test]
fn test_execute_edge_list_with_type() {
let router = QueryRouter::new();
let r1 = router.execute("NODE CREATE person").unwrap();
let r2 = router.execute("NODE CREATE person").unwrap();
let id1 = match r1 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
let id2 = match r2 {
QueryResult::Ids(ids) => ids[0],
_ => panic!("Expected Ids"),
};
router
.execute(&format!("EDGE CREATE {id1} -> {id2} : typed_edge"))
.unwrap();
let result = router.execute("EDGE LIST typed_edge").unwrap();
match result {
QueryResult::Edges(edges) => assert!(!edges.is_empty()),
other => panic!("Expected Edges, got: {other:?}"),
}
}
#[test]
fn test_execute_update_no_where() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE upd_all (id INT, val TEXT)")
.unwrap();
router
.execute("INSERT INTO upd_all (id, val) VALUES (1, 'old')")
.unwrap();
router
.execute("INSERT INTO upd_all (id, val) VALUES (2, 'old')")
.unwrap();
let result = router.execute("UPDATE upd_all SET val = 'new'").unwrap();
match result {
QueryResult::Count(c) => assert_eq!(c, 2),
other => panic!("Expected Count, got: {other:?}"),
}
}
#[test]
fn test_execute_delete_all() {
let router = QueryRouter::new();
router.execute("CREATE TABLE del_all (id INT)").unwrap();
router
.execute("INSERT INTO del_all (id) VALUES (1)")
.unwrap();
router
.execute("INSERT INTO del_all (id) VALUES (2)")
.unwrap();
let result = router.execute("DELETE FROM del_all").unwrap();
match result {
QueryResult::Count(c) => assert_eq!(c, 2),
other => panic!("Expected Count, got: {other:?}"),
}
}
#[test]
fn test_execute_select_specific_columns() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE proj_test (id INT, name TEXT, age INT)")
.unwrap();
router
.execute("INSERT INTO proj_test (id, name, age) VALUES (1, 'alice', 30)")
.unwrap();
let result = router.execute("SELECT name, age FROM proj_test").unwrap();
match result {
QueryResult::Rows(rows) => {
assert_eq!(rows.len(), 1);
},
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_select_where_not_equal() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE neq_test (id INT, val TEXT)")
.unwrap();
router
.execute("INSERT INTO neq_test (id, val) VALUES (1, 'a')")
.unwrap();
router
.execute("INSERT INTO neq_test (id, val) VALUES (2, 'b')")
.unwrap();
let result = router
.execute("SELECT * FROM neq_test WHERE val != 'a'")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_execute_select_where_between() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE betw_test (id INT, val INT)")
.unwrap();
for i in 1..=10 {
router
.execute(&format!(
"INSERT INTO betw_test (id, val) VALUES ({i}, {i})"
))
.unwrap();
}
let result = router
.execute("SELECT * FROM betw_test WHERE val >= 3 AND val <= 7")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 5),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_empty_command_error() {
let router = QueryRouter::new();
assert!(router.execute("").is_err());
assert!(router.execute(" ").is_err());
}
#[test]
fn test_parser_first_unknown_command_error() {
let router = QueryRouter::new();
let err = router.execute("FROBNICATE something").unwrap_err();
assert!(
matches!(err, RouterError::UnknownCommand(_))
|| matches!(err, RouterError::ParseError(_))
);
}
#[test]
fn test_parser_first_create_table_and_insert() {
let router = QueryRouter::new();
router.execute("CREATE TABLE pf (x INT, y TEXT)").unwrap();
router
.execute("INSERT INTO pf (x, y) VALUES (1, 'hello')")
.unwrap();
router
.execute("INSERT INTO pf (x, y) VALUES (2, 'world')")
.unwrap();
let result = router.execute("SELECT * FROM pf").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_update() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE pfu (id INT, val TEXT)")
.unwrap();
router
.execute("INSERT INTO pfu (id, val) VALUES (1, 'old')")
.unwrap();
router
.execute("UPDATE pfu SET val = 'new' WHERE id = 1")
.unwrap();
let result = router
.execute("SELECT * FROM pfu WHERE val = 'new'")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_delete_without_from() {
let router = QueryRouter::new();
router.execute("CREATE TABLE pfd (id INT)").unwrap();
router.execute("INSERT INTO pfd (id) VALUES (1)").unwrap();
router.execute("INSERT INTO pfd (id) VALUES (2)").unwrap();
router.execute("DELETE pfd WHERE id = 1").unwrap();
let result = router.execute("SELECT * FROM pfd").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_delete_with_from() {
let router = QueryRouter::new();
router.execute("CREATE TABLE pfd2 (id INT)").unwrap();
router.execute("INSERT INTO pfd2 (id) VALUES (1)").unwrap();
router.execute("DELETE FROM pfd2 WHERE id = 1").unwrap();
let result = router.execute("SELECT * FROM pfd2").unwrap();
match result {
QueryResult::Rows(rows) => assert!(rows.is_empty()),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_node_create_with_brace_props() {
let router = QueryRouter::new();
let result = router
.execute("NODE CREATE person { name: 'Alice', age: 30 }")
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
other => panic!("Expected Ids, got: {other:?}"),
}
}
#[test]
fn test_parser_first_edge_create_default_label() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE person { name: 'A' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let n2 = match router.execute("NODE CREATE person { name: 'B' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let result = router
.execute(&format!("EDGE CREATE {n1} -> {n2}"))
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
other => panic!("Expected Ids, got: {other:?}"),
}
}
#[test]
fn test_parser_first_edge_create_explicit_label() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE person { name: 'X' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let n2 = match router.execute("NODE CREATE person { name: 'Y' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let result = router
.execute(&format!("EDGE CREATE {n1} -> {n2} : knows"))
.unwrap();
match result {
QueryResult::Ids(ids) => assert_eq!(ids.len(), 1),
other => panic!("Expected Ids, got: {other:?}"),
}
}
#[test]
fn test_parser_first_neighbors_default_direction() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE user { name: 'Hub' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let n2 = match router
.execute("NODE CREATE user { name: 'Spoke' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : friend"))
.unwrap();
let result = router.execute(&format!("NEIGHBORS {n1}")).unwrap();
match result {
QueryResult::Ids(ids) => assert!(!ids.is_empty()),
other => panic!("Expected Ids, got: {other:?}"),
}
}
#[test]
fn test_parser_first_embed_shorthand() {
let router = QueryRouter::new();
router.execute("EMBED 'short1' [1.0, 0.0, 0.0]").unwrap();
let result = router.execute("SIMILAR 'short1' LIMIT 1").unwrap();
match result {
QueryResult::Similar(sims) => assert!(!sims.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_parser_first_embed_store_with_negative_values() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'neg1' [-0.5, 0.3, -0.8]")
.unwrap();
router
.execute("EMBED STORE 'neg2' [0.5, -0.3, 0.8]")
.unwrap();
let result = router.execute("SIMILAR 'neg1' LIMIT 2").unwrap();
match result {
QueryResult::Similar(sims) => assert!(!sims.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_parser_first_similar_top_keyword() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'top1' [1.0, 0.0]").unwrap();
router.execute("EMBED STORE 'top2' [0.9, 0.1]").unwrap();
let result = router.execute("SIMILAR 'top1' TOP 2").unwrap();
match result {
QueryResult::Similar(sims) => assert!(!sims.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_parser_first_similar_metric_before_limit() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'mb1' [1.0, 0.0]").unwrap();
router.execute("EMBED STORE 'mb2' [0.8, 0.2]").unwrap();
let result = router.execute("SIMILAR 'mb1' COSINE LIMIT 2").unwrap();
match result {
QueryResult::Similar(sims) => assert!(!sims.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_parser_first_similar_limit_before_metric() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'lm1' [1.0, 0.0]").unwrap();
router.execute("EMBED STORE 'lm2' [0.8, 0.2]").unwrap();
let result = router.execute("SIMILAR 'lm1' LIMIT 2 EUCLIDEAN").unwrap();
match result {
QueryResult::Similar(sims) => assert!(!sims.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_parser_first_find_nodes_plural_parsed() {
let router = QueryRouter::new();
router
.execute("NODE CREATE animal { species: 'cat' }")
.unwrap();
let result = router.execute("FIND NODES animal");
assert!(result.is_ok(), "FIND NODES failed: {result:?}");
}
#[test]
fn test_parser_first_find_edges_plural_parsed() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE p { v: 1 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let n2 = match router.execute("NODE CREATE p { v: 2 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : knows"))
.unwrap();
let result = router.execute("FIND EDGES knows");
assert!(result.is_ok(), "FIND EDGES failed: {result:?}");
}
#[test]
fn test_parser_first_create_index() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE idx_t (id INT, name TEXT)")
.unwrap();
let result = router.execute("CREATE INDEX idx_name ON idx_t(name)");
assert!(result.is_ok(), "CREATE INDEX failed: {result:?}");
}
#[test]
fn test_parser_first_keyword_column_names_insert() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE kc (id INT, status TEXT, type TEXT)")
.unwrap();
router
.execute("INSERT INTO kc (id, status, type) VALUES (1, 'active', 'user')")
.unwrap();
let result = router.execute("SELECT * FROM kc").unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_keyword_column_names_update() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE ku (id INT, status TEXT)")
.unwrap();
router
.execute("INSERT INTO ku (id, status) VALUES (1, 'old')")
.unwrap();
router
.execute("UPDATE ku SET status = 'new' WHERE id = 1")
.unwrap();
let result = router
.execute("SELECT * FROM ku WHERE status = 'new'")
.unwrap();
match result {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_cache_invalidation_on_write() {
let mut router = QueryRouter::new();
router.init_cache();
router.execute("CREATE TABLE ci (id INT)").unwrap();
router.execute("INSERT INTO ci (id) VALUES (1)").unwrap();
let r1 = router.execute("SELECT * FROM ci").unwrap();
match &r1 {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 1),
other => panic!("Expected Rows, got: {other:?}"),
}
router.execute("INSERT INTO ci (id) VALUES (2)").unwrap();
let r2 = router.execute("SELECT * FROM ci").unwrap();
match r2 {
QueryResult::Rows(rows) => assert_eq!(rows.len(), 2),
other => panic!("Expected Rows, got: {other:?}"),
}
}
#[test]
fn test_parser_first_node_get() {
let router = QueryRouter::new();
let node_id = match router
.execute("NODE CREATE city { name: 'Berlin' }")
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let result = router.execute(&format!("NODE GET {node_id}")).unwrap();
match result {
QueryResult::Nodes(nodes) => {
assert_eq!(nodes.len(), 1);
},
other => panic!("Expected Nodes, got: {other:?}"),
}
}
#[test]
fn test_parser_first_node_delete() {
let router = QueryRouter::new();
let node_id = match router.execute("NODE CREATE temp { val: 1 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
router.execute(&format!("NODE DELETE {node_id}")).unwrap();
let result = router.execute(&format!("NODE GET {node_id}"));
assert!(result.is_err());
}
#[test]
fn test_parser_first_edge_delete() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE t { v: 1 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let n2 = match router.execute("NODE CREATE t { v: 2 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let edge_id = match router
.execute(&format!("EDGE CREATE {n1} -> {n2} : linked"))
.unwrap()
{
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
router.execute(&format!("EDGE DELETE {edge_id}")).unwrap();
}
#[test]
fn test_parser_first_embed_get() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'eg_key' [1.0, 2.0, 3.0]")
.unwrap();
let result = router.execute("EMBED GET 'eg_key'").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("1.0"));
assert!(s.contains("2.0"));
assert!(s.contains("3.0"));
},
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_parser_first_embed_delete() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'ed_key' [1.0, 0.0]").unwrap();
router.execute("EMBED DELETE 'ed_key'").unwrap();
let result = router.execute("EMBED GET 'ed_key'");
assert!(result.is_err());
}
#[test]
fn test_parser_first_embed_batch() {
let router = QueryRouter::new();
router
.execute("EMBED BATCH [('b1', [1.0, 0.0]), ('b2', [0.0, 1.0])]")
.unwrap();
let r1 = router.execute("EMBED GET 'b1'").unwrap();
match r1 {
QueryResult::Value(s) => assert!(s.contains("1.0")),
other => panic!("Expected Embedding, got: {other:?}"),
}
}
#[test]
fn test_parser_first_graph_aggregate_on_node() {
let router = QueryRouter::new();
router
.execute("NODE CREATE worker { salary: 50000 }")
.unwrap();
router
.execute("NODE CREATE worker { salary: 70000 }")
.unwrap();
let result = router
.execute("AGGREGATE NODE PROPERTY salary SUM ON worker")
.unwrap();
match result {
QueryResult::Aggregate(AggregateResultValue::Sum(v)) => {
assert!((v - 120_000.0).abs() < 0.01);
},
other => panic!("Expected Aggregate Sum, got: {other:?}"),
}
}
#[test]
fn test_parser_first_describe_table() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE desc_t (id INT, name TEXT)")
.unwrap();
let result = router.execute("DESCRIBE TABLE desc_t").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("desc_t"));
assert!(s.contains("id"));
assert!(s.contains("name"));
},
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_parser_first_show_tables() {
let router = QueryRouter::new();
router.execute("CREATE TABLE show1 (id INT)").unwrap();
let result = router.execute("SHOW TABLES").unwrap();
match result {
QueryResult::TableList(tables) => {
assert!(tables.iter().any(|t| t == "show1"));
},
other => panic!("Expected TableList, got: {other:?}"),
}
}
#[test]
fn test_parser_first_path_shortest() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE city { name: 'A' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let n2 = match router.execute("NODE CREATE city { name: 'B' }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : road"))
.unwrap();
let result = router.execute(&format!("PATH {n1} -> {n2}")).unwrap();
match result {
QueryResult::Path(path) => assert!(!path.is_empty()),
other => panic!("Expected Path, got: {other:?}"),
}
}
#[test]
fn test_parser_first_node_list() {
let router = QueryRouter::new();
router
.execute("NODE CREATE fruit { name: 'apple' }")
.unwrap();
router
.execute("NODE CREATE fruit { name: 'banana' }")
.unwrap();
let result = router.execute("NODE LIST").unwrap();
match result {
QueryResult::Nodes(nodes) => assert!(nodes.len() >= 2),
other => panic!("Expected Nodes, got: {other:?}"),
}
}
#[test]
fn test_parser_first_edge_list() {
let router = QueryRouter::new();
let n1 = match router.execute("NODE CREATE thing { v: 1 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
let n2 = match router.execute("NODE CREATE thing { v: 2 }").unwrap() {
QueryResult::Ids(ids) => ids[0],
other => panic!("Expected Ids, got: {other:?}"),
};
router
.execute(&format!("EDGE CREATE {n1} -> {n2} : conn"))
.unwrap();
let result = router.execute("EDGE LIST").unwrap();
match result {
QueryResult::Edges(edges) => assert!(!edges.is_empty()),
other => panic!("Expected Edges, got: {other:?}"),
}
}
#[test]
fn test_parser_first_embed_negative_float_vector() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'nf1' [-1.5, 2.0, -0.5, 0.0]")
.unwrap();
let result = router.execute("EMBED GET 'nf1'").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("-1.5"));
assert!(s.contains("-0.5"));
},
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_parser_first_embed_negative_integer_in_vector() {
let router = QueryRouter::new();
router.execute("EMBED STORE 'ni1' [-1, 2, -3, 0]").unwrap();
let result = router.execute("EMBED GET 'ni1'").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("-1."));
assert!(s.contains("-3."));
},
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_parser_first_similar_with_collection() {
let router = QueryRouter::new();
router
.execute("EMBED STORE 'sc1' [1.0, 0.0] INTO grp")
.unwrap();
router
.execute("EMBED STORE 'sc2' [0.9, 0.1] INTO grp")
.unwrap();
let result = router.execute("SIMILAR 'sc1' LIMIT 2 INTO grp").unwrap();
match result {
QueryResult::Similar(sims) => assert!(!sims.is_empty()),
other => panic!("Expected Similar, got: {other:?}"),
}
}
#[test]
fn test_parser_first_create_table_if_not_exists() {
let router = QueryRouter::new();
router
.execute("CREATE TABLE IF NOT EXISTS ine (id INT)")
.unwrap();
router
.execute("CREATE TABLE IF NOT EXISTS ine (id INT)")
.unwrap();
}
#[test]
fn test_parser_first_drop_table() {
let router = QueryRouter::new();
router.execute("CREATE TABLE dt (id INT)").unwrap();
router.execute("DROP TABLE dt").unwrap();
let result = router.execute("SELECT * FROM dt");
assert!(result.is_err());
}
#[test]
fn test_spatial_accessor_returns_spatial_index() {
let router = QueryRouter::new();
let spatial = router.spatial();
let guard = spatial.read();
assert_eq!(
guard.len(),
0,
"New router should have an empty spatial index"
);
}
#[test]
fn test_execute_empty_statement_semicolon() {
let router = QueryRouter::new();
let result = router.execute(";").unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_execute_describe_node() {
let router = QueryRouter::new();
router.execute("NODE CREATE person").unwrap();
let result = router.execute("DESCRIBE NODE person").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("person"), "Should mention the label");
assert!(s.contains("NODE LIST"), "Should reference NODE LIST");
},
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_execute_describe_edge() {
let router = QueryRouter::new();
let result = router.execute("DESCRIBE EDGE follows").unwrap();
match result {
QueryResult::Value(s) => {
assert!(s.contains("follows"), "Should mention the edge type");
assert!(s.contains("EDGE LIST"), "Should reference EDGE LIST");
},
other => panic!("Expected Value, got: {other:?}"),
}
}
#[test]
fn test_cypher_merge_via_execute_statement() {
use neumann_parser::cypher::{CypherElement, CypherMergeStmt, CypherNode, CypherPattern};
use neumann_parser::{Ident, Span};
let router = QueryRouter::new();
let stmt = Statement::new(
StatementKind::CypherMerge(CypherMergeStmt {
pattern: CypherPattern {
variable: None,
elements: vec![CypherElement::Node(CypherNode {
variable: Some(Ident::new("n", Span::from_offsets(0, 1))),
labels: vec![Ident::new("TestLabel", Span::from_offsets(0, 9))],
properties: vec![],
})],
},
on_create: vec![],
on_match: vec![],
}),
Span::from_offsets(0, 1),
);
let result = router.execute_statement(&stmt).unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn test_cypher_create_via_execute_statement() {
use neumann_parser::cypher::{CypherCreateStmt, CypherElement, CypherNode, CypherPattern};
use neumann_parser::{Ident, Span};
let router = QueryRouter::new();
let stmt = Statement::new(
StatementKind::CypherCreate(CypherCreateStmt {
patterns: vec![CypherPattern {
variable: None,
elements: vec![CypherElement::Node(CypherNode {
variable: Some(Ident::new("n", Span::from_offsets(0, 1))),
labels: vec![Ident::new("CypherNode", Span::from_offsets(0, 10))],
properties: vec![],
})],
}],
}),
Span::from_offsets(0, 1),
);
let result = router.execute_statement(&stmt).unwrap();
assert!(matches!(result, QueryResult::Ids(_)));
}
#[test]
fn test_cypher_delete_via_execute_statement() {
use neumann_parser::cypher::CypherDeleteStmt;
use neumann_parser::{Ident, Span};
let router = QueryRouter::new();
router.execute("NODE CREATE testdel").unwrap();
let stmt = Statement::new(
StatementKind::CypherDelete(CypherDeleteStmt {
detach: false,
variables: vec![Expr::new(
ExprKind::Ident(Ident::new("n", Span::from_offsets(0, 1))),
Span::from_offsets(0, 1),
)],
}),
Span::from_offsets(0, 1),
);
let _ = router.execute_statement(&stmt);
}
#[test]
fn test_cypher_match_via_execute_statement() {
use neumann_parser::cypher::{
CypherElement, CypherMatchStmt, CypherNode, CypherPattern, CypherReturn,
CypherReturnItem,
};
use neumann_parser::{Ident, Span};
let router = QueryRouter::new();
router.execute("NODE CREATE matchlabel").unwrap();
let stmt = Statement::new(
StatementKind::CypherMatch(CypherMatchStmt {
optional: false,
patterns: vec![CypherPattern {
variable: None,
elements: vec![CypherElement::Node(CypherNode {
variable: Some(Ident::new("n", Span::from_offsets(0, 1))),
labels: vec![Ident::new("matchlabel", Span::from_offsets(0, 10))],
properties: vec![],
})],
}],
where_clause: None,
return_clause: CypherReturn {
distinct: false,
items: vec![CypherReturnItem {
expr: Expr::new(
ExprKind::Ident(Ident::new("n", Span::from_offsets(0, 1))),
Span::from_offsets(0, 1),
),
alias: None,
}],
},
order_by: vec![],
skip: None,
limit: None,
}),
Span::from_offsets(0, 1),
);
let result = router.execute_statement(&stmt);
let _ = result;
}
#[test]
fn test_drop_index_invalid_syntax_via_execute_statement() {
use neumann_parser::{DropIndexStmt, Span};
let router = QueryRouter::new();
let stmt = Statement::new(
StatementKind::DropIndex(DropIndexStmt {
if_exists: false,
name: None,
table: None,
column: None,
}),
Span::from_offsets(0, 1),
);
let result = router.execute_statement(&stmt);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Invalid DROP INDEX syntax"),
"Error should mention invalid syntax, got: {err_msg}"
);
}
#[test]
fn test_paginated_edge_list() {
let router = QueryRouter::new();
let n1 = router.graph.create_node("a", HashMap::new()).unwrap();
let n2 = router.graph.create_node("b", HashMap::new()).unwrap();
router
.graph
.create_edge(n1, n2, "knows", HashMap::new(), true)
.unwrap();
let options = PaginationOptions::new()
.with_page_size(10)
.with_count_total(true);
let result = router.execute_paginated("EDGE LIST", options);
assert!(result.is_ok());
let paged = result.unwrap();
assert!(paged.total_count.is_some());
assert!(matches!(paged.result, QueryResult::Edges(_)));
}
#[test]
fn test_create_index_no_columns_via_execute_statement() {
use neumann_parser::{CreateIndexStmt, Ident, Span};
let router = QueryRouter::new();
router
.execute("CREATE TABLE cidx (id INT, name TEXT)")
.unwrap();
let stmt = Statement::new(
StatementKind::CreateIndex(CreateIndexStmt {
unique: false,
if_not_exists: false,
name: Ident::new("idx_empty", Span::from_offsets(0, 9)),
table: Ident::new("cidx", Span::from_offsets(0, 4)),
columns: vec![],
}),
Span::from_offsets(0, 1),
);
let result = router.execute_statement(&stmt).unwrap();
assert!(matches!(result, QueryResult::Empty));
}
#[test]
fn test_graph_pattern_dispatch() {
use neumann_parser::{PatternSpec, Span};
let router = QueryRouter::new();
router.execute("NODE CREATE gp_person").unwrap();
let stmt = Statement::new(
StatementKind::GraphPattern(GraphPatternStmt {
operation: GraphPatternOp::Match {
pattern: PatternSpec {
nodes: vec![],
edges: vec![],
},
limit: None,
},
}),
Span::from_offsets(0, 1),
);
let result = router.execute_statement(&stmt);
let _ = result;
}
}