#![cfg(feature = "native-v3")]
use sqlitegraph::{
SqliteGraphError,
backend::{BackendDirection, GraphBackend, NodeSpec},
pattern::{NodeConstraint, PatternLeg, PatternQuery},
};
use sqlitegraph::backend::native::v3::V3Backend;
use tempfile::TempDir;
fn create_v3_backend() -> (TempDir, V3Backend) {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test_v3.graph");
let backend = V3Backend::create(&db_path).unwrap();
(temp_dir, backend)
}
#[test]
fn test_v3_pattern_search_matches_kind_chain() {
let (_temp_dir, backend) = create_v3_backend();
let f1 = backend
.insert_node(NodeSpec {
kind: "Function".to_string(),
name: "A_func".to_string(),
file_path: None,
data: serde_json::json!({"name": "A_func"}),
})
.unwrap();
let f2 = backend
.insert_node(NodeSpec {
kind: "Function".to_string(),
name: "B_func".to_string(),
file_path: None,
data: serde_json::json!({"name": "B_func"}),
})
.unwrap();
let f3 = backend
.insert_node(NodeSpec {
kind: "Function".to_string(),
name: "C_func".to_string(),
file_path: None,
data: serde_json::json!({"name": "C_func"}),
})
.unwrap();
let s1 = backend
.insert_node(NodeSpec {
kind: "Struct".to_string(),
name: "S_alpha".to_string(),
file_path: None,
data: serde_json::json!({"name": "S_alpha"}),
})
.unwrap();
let s2 = backend
.insert_node(NodeSpec {
kind: "Struct".to_string(),
name: "S_beta".to_string(),
file_path: None,
data: serde_json::json!({"name": "S_beta"}),
})
.unwrap();
backend
.insert_edge(sqlitegraph::backend::EdgeSpec {
from: f1,
to: f2,
edge_type: "CALLS".to_string(),
data: serde_json::json!({}),
})
.unwrap();
backend
.insert_edge(sqlitegraph::backend::EdgeSpec {
from: f2,
to: s1,
edge_type: "USES".to_string(),
data: serde_json::json!({}),
})
.unwrap();
backend
.insert_edge(sqlitegraph::backend::EdgeSpec {
from: f1,
to: f3,
edge_type: "CALLS".to_string(),
data: serde_json::json!({}),
})
.unwrap();
backend
.insert_edge(sqlitegraph::backend::EdgeSpec {
from: f3,
to: s2,
edge_type: "USES".to_string(),
data: serde_json::json!({}),
})
.unwrap();
backend
.insert_edge(sqlitegraph::backend::EdgeSpec {
from: f2,
to: f3,
edge_type: "CALLS".to_string(),
data: serde_json::json!({}),
})
.unwrap();
let pattern = PatternQuery {
root: Some(NodeConstraint::kind("Function")),
legs: vec![
PatternLeg {
direction: BackendDirection::Outgoing,
edge_type: Some("CALLS".into()),
constraint: Some(NodeConstraint::kind("Function")),
},
PatternLeg {
direction: BackendDirection::Outgoing,
edge_type: Some("USES".into()),
constraint: Some(NodeConstraint::kind("Struct")),
},
],
};
let matches = backend
.pattern_search(sqlitegraph::SnapshotId::current(), f1, &pattern)
.expect("pattern search should succeed");
let sequences: Vec<Vec<i64>> = matches.into_iter().map(|m| m.nodes).collect();
assert_eq!(sequences, vec![vec![f1, f2, s1], vec![f1, f3, s2]]);
}
#[test]
fn test_v3_snapshot_import_returns_unimplemented_error() {
let (_temp_dir, backend) = create_v3_backend();
let result = backend.snapshot_import(_temp_dir.path());
match result {
Err(SqliteGraphError::Unsupported(msg)) => {
assert!(
msg.contains("snapshot_import"),
"Error message should mention snapshot_import"
);
assert!(
msg.contains("V3"),
"Error message should mention V3 backend"
);
}
Ok(metadata) => {
panic!(
"snapshot_import should NOT succeed with fake zeros! Got: {:?} \
This indicates the stub is still pretending to succeed.",
metadata
);
}
Err(other) => {
panic!("Expected Unsupported error, got: {:?}", other);
}
}
}
#[test]
fn test_v3_query_nodes_by_name_pattern_substring_not_glob() {
let (_temp_dir, backend) = create_v3_backend();
let _user = backend
.insert_node(NodeSpec {
kind: "User".to_string(),
name: "SuperUser".to_string(),
file_path: None,
data: serde_json::json!({}),
})
.unwrap();
let _admin = backend
.insert_node(NodeSpec {
kind: "Admin".to_string(),
name: "UserAdmin".to_string(),
file_path: None,
data: serde_json::json!({}),
})
.unwrap();
let _root = backend
.insert_node(NodeSpec {
kind: "Root".to_string(),
name: "root".to_string(),
file_path: None,
data: serde_json::json!({}),
})
.unwrap();
let result = backend
.query_nodes_by_name_pattern(sqlitegraph::SnapshotId::current(), "User")
.unwrap();
assert_eq!(
result.len(),
2,
"V3 substring matching should find both 'SuperUser' and 'UserAdmin'"
);
}
#[test]
fn test_v3_query_nodes_by_kind_uses_kind_index_correctly() {
let (_temp_dir, backend) = create_v3_backend();
let _user1 = backend
.insert_node(NodeSpec {
kind: "User".to_string(),
name: "alice".to_string(),
file_path: None,
data: serde_json::json!({}),
})
.unwrap();
let _user2 = backend
.insert_node(NodeSpec {
kind: "User".to_string(),
name: "bob".to_string(),
file_path: None,
data: serde_json::json!({}),
})
.unwrap();
let _doc = backend
.insert_node(NodeSpec {
kind: "Document".to_string(),
name: "test.txt".to_string(),
file_path: None,
data: serde_json::json!({}),
})
.unwrap();
let result = backend
.query_nodes_by_kind(sqlitegraph::SnapshotId::current(), "User")
.unwrap();
assert_eq!(result.len(), 2, "Should find exactly 2 User nodes");
}