#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use crate::edge::{EdgeSchema, EdgeStore, EdgeStrategy, RecordForm};
use crate::engine::data_store::EdgeTableKey;
use crate::types::StoragePropertyDef;
use crate::vertex::{IdKey, ShardedVertexTable, VertexSchema};
use linkrs_core::types::{LabelId, VertexId};
use linkrs_core::Value;
use crate::engine::transaction::ops::{
create_edge_type_undo, create_vertex_type_undo, delete_edge_type, delete_vertex_type,
AddEdgeParams, DeleteEdgeTypeParams, RevertDeleteEdgeParams, TransactionOps,
};
fn create_vertex_table(label: LabelId, name: &str) -> Arc<ShardedVertexTable> {
let schema = VertexSchema {
label_id: label,
label_name: name.to_string(),
properties: vec![
StoragePropertyDef::new("id".into(), linkrs_core::DataType::String),
StoragePropertyDef::new("name".into(), linkrs_core::DataType::String),
StoragePropertyDef::new("age".into(), linkrs_core::DataType::BigInt),
],
primary_key_index: 0,
schema_version: 1,
};
Arc::new(ShardedVertexTable::new(label, name.to_string(), schema))
}
fn create_edge_table(
edge_label: LabelId,
src_label: LabelId,
dst_label: LabelId,
name: &str,
) -> EdgeStore {
let schema = EdgeSchema {
label_id: edge_label,
label_name: name.to_string(),
src_label,
dst_label,
properties: vec![StoragePropertyDef {
name: "since".into(),
data_type: linkrs_core::DataType::Int,
nullable: false,
default_value: Some(Value::Int(0)),
}],
oe_strategy: EdgeStrategy::Multiple,
ie_strategy: EdgeStrategy::Multiple,
schema_version: 1,
record_form: RecordForm::default(),
};
EdgeStore::new(schema).expect("Failed to create EdgeStore")
}
#[test]
#[allow(clippy::useless_vec)]
fn test_add_vertex_int_id() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
let vid = VertexId::try_from_int64(100).expect("test vertex id");
let properties = vec![
("name".into(), Value::string("Alice")),
("age".into(), Value::BigInt(30)),
];
let result = TransactionOps::add_vertex(&vertex_tables, 0, vid, &properties, 1);
assert!(result.is_ok());
let table = vertex_tables.get(&0).unwrap();
let internal = table.get_internal_id_by_i64(100, 1);
assert!(internal.is_some());
assert_eq!(
result.unwrap(),
VertexId::try_from_int64(internal.unwrap() as i64).expect("test vertex id")
);
}
#[test]
#[allow(clippy::useless_vec)]
fn test_add_vertex_string_id() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
let vid = VertexId::try_from_string("user-alice").expect("test vertex id");
let properties = vec![
("name".into(), Value::string("Alice")),
("age".into(), Value::BigInt(30)),
];
let result = TransactionOps::add_vertex(&vertex_tables, 0, vid, &properties, 1);
assert!(result.is_ok());
let table = vertex_tables.get(&0).unwrap();
let internal = table.get_internal_id("user-alice", 1);
assert!(internal.is_some());
}
#[test]
fn test_add_vertex_label_not_found() {
let vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
let vid = VertexId::try_from_int64(1).expect("test vertex id");
let result = TransactionOps::add_vertex(&vertex_tables, 99, vid, &[], 1);
assert!(result.is_err());
}
#[test]
fn test_add_edge() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
vertex_tables.insert(1, create_vertex_table(1, "Person"));
let mut edge_tables: HashMap<EdgeTableKey, Arc<RwLock<EdgeStore>>> = HashMap::new();
edge_tables.insert(
EdgeTableKey::new(0, 1, 0),
Arc::new(RwLock::new(create_edge_table(0, 0, 1, "KNOWS"))),
);
let vid1 = VertexId::try_from_int64(100).expect("test vertex id");
let vid2 = VertexId::try_from_int64(101).expect("test vertex id");
TransactionOps::add_vertex(&vertex_tables, 0, vid1, &[], 1).unwrap();
TransactionOps::add_vertex(&vertex_tables, 1, vid2, &[], 1).unwrap();
let src_internal = vertex_tables
.get(&0)
.unwrap()
.get_internal_id_by_i64(100, 1)
.unwrap();
let dst_internal = vertex_tables
.get(&1)
.unwrap()
.get_internal_id_by_i64(101, 1)
.unwrap();
let params = AddEdgeParams {
src_label: 0,
src_vid: src_internal,
dst_label: 1,
dst_vid: dst_internal,
edge_label: 0,
rank: 0,
};
let result = TransactionOps::add_edge(&mut edge_tables, &vertex_tables, params, &[], 1);
assert!(result.is_ok(), "add_edge failed: {:?}", result.err());
}
#[test]
fn test_add_edge_missing_src_label() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
let mut edge_tables: HashMap<EdgeTableKey, Arc<RwLock<EdgeStore>>> = HashMap::new();
edge_tables.insert(
EdgeTableKey::new(0, 1, 0),
Arc::new(RwLock::new(create_edge_table(0, 0, 1, "KNOWS"))),
);
let params = AddEdgeParams {
src_label: 0,
src_vid: 0,
dst_label: 1,
dst_vid: 0,
edge_label: 0,
rank: 0,
};
let result = TransactionOps::add_edge(&mut edge_tables, &vertex_tables, params, &[], 1);
assert!(result.is_err());
}
#[test]
fn test_resolve_vertex_id() {
let table = create_vertex_table(0, "Person");
table
.insert_by_i64(100, &[("name".into(), Value::string("Alice"))], 1)
.unwrap();
table
.insert("user-bob-ext", &[("name".into(), Value::string("Bob"))], 1)
.unwrap();
let resolved_int = TransactionOps::resolve_vertex_id(
&table,
VertexId::try_from_int64(100).expect("test vertex id"),
1,
);
assert!(resolved_int.is_some());
assert_eq!(
table.get_external_id(resolved_int.unwrap(), 1),
Some(IdKey::Int(100))
);
let resolved_str = TransactionOps::resolve_vertex_id(
&table,
VertexId::try_from_string("user-bob-ext").expect("test vertex id"),
1,
);
assert!(resolved_str.is_some());
assert_ne!(resolved_int, resolved_str);
assert_eq!(
table.get_external_id(resolved_str.unwrap(), 1),
Some(IdKey::Text("user-bob-ext".to_string()))
);
let not_found = TransactionOps::resolve_vertex_id(
&table,
VertexId::try_from_int64(999).expect("test vertex id"),
1,
);
assert_eq!(not_found, None);
}
#[test]
fn test_delete_vertex() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
TransactionOps::add_vertex(
&vertex_tables,
0,
VertexId::try_from_int64(1).expect("test vertex id"),
&[],
1,
)
.unwrap();
let result = TransactionOps::delete_vertex(
&vertex_tables,
0,
VertexId::try_from_int64(1).expect("test vertex id"),
2,
);
assert!(result.is_ok());
let table = vertex_tables.get(&0).unwrap();
let internal = table.get_internal_id_by_i64(1, 2);
assert!(internal.is_none());
let v = internal.and_then(|id| table.get_by_internal_id_offline(id, 2));
assert!(v.is_none());
}
#[test]
fn test_update_vertex_property_by_vid() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
let vid = VertexId::try_from_int64(1).expect("test vertex id");
TransactionOps::add_vertex(
&vertex_tables,
0,
vid,
&[("name".into(), Value::string("Alice"))],
1,
)
.unwrap();
let result = TransactionOps::update_vertex_property_by_vid(
&vertex_tables,
0,
VertexId::try_from_int64(1).expect("test vertex id"),
"name",
&Value::string("AliceUpdated"),
2,
);
assert!(result.is_ok());
let table = vertex_tables.get(&0).unwrap();
let internal = table.get_internal_id_by_i64(1, 2).unwrap();
let record = table.get_by_internal_id_offline(internal, 2).unwrap();
let name_val = record
.properties
.iter()
.find(|(k, _)| &**k == "name")
.map(|(_, v)| v);
assert_eq!(name_val, Some(&Value::string("AliceUpdated")));
}
#[test]
fn test_delete_vertex_type_cascades_to_edge_types() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
vertex_tables.insert(1, create_vertex_table(1, "Employee"));
let mut edge_tables: HashMap<EdgeTableKey, Arc<RwLock<EdgeStore>>> = HashMap::new();
edge_tables.insert(
EdgeTableKey::new(0, 0, 0),
Arc::new(RwLock::new(create_edge_table(0, 0, 0, "KNOWS"))),
);
edge_tables.insert(
EdgeTableKey::new(0, 1, 1),
Arc::new(RwLock::new(create_edge_table(1, 0, 1, "WORKS_AT"))),
);
let mut vertex_label_names: HashMap<String, LabelId> = HashMap::new();
vertex_label_names.insert("Person".into(), 0);
vertex_label_names.insert("Employee".into(), 1);
let mut edge_label_names: HashMap<String, LabelId> = HashMap::new();
edge_label_names.insert("KNOWS".into(), 0);
edge_label_names.insert("WORKS_AT".into(), 1);
delete_vertex_type(
&mut vertex_tables,
&mut edge_tables,
&mut vertex_label_names,
&mut edge_label_names,
1,
)
.unwrap();
assert!(!vertex_tables.contains_key(&1));
assert!(!vertex_label_names.contains_key("Employee"));
assert!(!edge_label_names.contains_key("WORKS_AT"));
assert!(edge_tables.contains_key(&EdgeTableKey::new(0, 0, 0)));
}
#[test]
fn test_delete_edge_type() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
vertex_tables.insert(1, create_vertex_table(1, "Person"));
let mut edge_tables: HashMap<EdgeTableKey, Arc<RwLock<EdgeStore>>> = HashMap::new();
edge_tables.insert(
EdgeTableKey::new(0, 1, 0),
Arc::new(RwLock::new(create_edge_table(0, 0, 1, "KNOWS"))),
);
let mut edge_label_names: HashMap<String, LabelId> = HashMap::new();
edge_label_names.insert("KNOWS".into(), 0);
let params = DeleteEdgeTypeParams {
src_label: 0,
dst_label: 1,
edge_label: 0,
};
delete_edge_type(&mut edge_tables, &mut edge_label_names, params).unwrap();
assert!(!edge_label_names.contains_key("KNOWS"));
assert!(!edge_tables.contains_key(&EdgeTableKey::new(0, 1, 0)));
}
#[test]
fn test_revert_delete_edge() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
vertex_tables.insert(0, create_vertex_table(0, "Person"));
let mut edge_tables: HashMap<EdgeTableKey, Arc<RwLock<EdgeStore>>> = HashMap::new();
edge_tables.insert(
EdgeTableKey::new(0, 0, 0),
Arc::new(RwLock::new(create_edge_table(0, 0, 0, "KNOWS"))),
);
TransactionOps::add_vertex(
&vertex_tables,
0,
VertexId::try_from_int64(1).expect("test vertex id"),
&[],
1,
)
.unwrap();
TransactionOps::add_vertex(
&vertex_tables,
0,
VertexId::try_from_int64(2).expect("test vertex id"),
&[],
1,
)
.unwrap();
let src_internal = vertex_tables
.get(&0)
.unwrap()
.get_internal_id_by_i64(1, 1)
.unwrap();
let dst_internal = vertex_tables
.get(&0)
.unwrap()
.get_internal_id_by_i64(2, 1)
.unwrap();
let add_params = AddEdgeParams {
src_label: 0,
src_vid: src_internal,
dst_label: 0,
dst_vid: dst_internal,
edge_label: 0,
rank: 0,
};
TransactionOps::add_edge(&mut edge_tables, &vertex_tables, add_params, &[], 1).unwrap();
{
let table = edge_tables.get(&EdgeTableKey::new(0, 0, 0)).unwrap();
let mut edge_store = table.write();
edge_store
.delete_edge(src_internal, dst_internal, 0, 2)
.unwrap();
}
let revert_params = RevertDeleteEdgeParams {
src_vid: src_internal,
dst_vid: dst_internal,
rank: 0,
};
let table = edge_tables.get(&EdgeTableKey::new(0, 0, 0)).unwrap();
let mut edge_store = table.write();
let result =
TransactionOps::revert_delete_edge_single_by_key(&mut edge_store, revert_params, 3);
assert!(result.is_ok());
}
#[test]
fn test_create_vertex_type_undo() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
let mut vertex_label_names: HashMap<String, LabelId> = HashMap::new();
let mut vertex_label_counter: LabelId = 0;
create_vertex_type_undo(
&mut vertex_tables,
&mut vertex_label_names,
&mut vertex_label_counter,
"Person",
)
.unwrap();
assert!(vertex_tables.contains_key(&0));
assert_eq!(vertex_label_names.get("Person"), Some(&0));
assert!(vertex_label_counter >= 1);
create_vertex_type_undo(
&mut vertex_tables,
&mut vertex_label_names,
&mut vertex_label_counter,
"Employee",
)
.unwrap();
assert!(vertex_tables.contains_key(&1));
assert_eq!(vertex_label_names.get("Employee"), Some(&1));
}
#[test]
fn test_create_edge_type_undo() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
let mut vertex_label_names: HashMap<String, LabelId> = HashMap::new();
let mut vertex_label_counter: LabelId = 0;
create_vertex_type_undo(
&mut vertex_tables,
&mut vertex_label_names,
&mut vertex_label_counter,
"Person",
)
.unwrap();
let mut edge_tables: HashMap<EdgeTableKey, Arc<RwLock<EdgeStore>>> = HashMap::new();
let mut edge_label_names: HashMap<String, LabelId> = HashMap::new();
let mut edge_label_counter: LabelId = 0;
let result = create_edge_type_undo(
&mut edge_tables,
&mut edge_label_names,
&mut edge_label_counter,
&vertex_tables,
"KNOWS",
"Person",
"Person",
);
assert!(result.is_ok());
let key = EdgeTableKey::new(0, 0, 0);
assert!(edge_tables.contains_key(&key));
assert_eq!(edge_label_names.get("KNOWS"), Some(&0));
}
#[test]
fn test_create_edge_type_undo_missing_vertex_label() {
let vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
let mut edge_tables: HashMap<EdgeTableKey, Arc<RwLock<EdgeStore>>> = HashMap::new();
let mut edge_label_names: HashMap<String, LabelId> = HashMap::new();
let mut edge_label_counter: LabelId = 0;
let result = create_edge_type_undo(
&mut edge_tables,
&mut edge_label_names,
&mut edge_label_counter,
&vertex_tables,
"KNOWS",
"Person",
"Person",
);
assert!(result.is_err());
}
#[test]
fn test_revert_rename_vertex_properties() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
let mut vertex_label_names: HashMap<String, LabelId> = HashMap::new();
let mut vertex_label_counter: LabelId = 0;
create_vertex_type_undo(
&mut vertex_tables,
&mut vertex_label_names,
&mut vertex_label_counter,
"Person",
)
.unwrap();
{
let table = vertex_tables.get_mut(&0).unwrap();
table
.add_property(crate::types::StoragePropertyDef::new(
"name".into(),
linkrs_core::DataType::String,
))
.unwrap();
}
{
let table = vertex_tables.get_mut(&0).unwrap();
table.rename_property("name", "full_name").unwrap();
}
let result = TransactionOps::revert_rename_vertex_properties(
&vertex_tables,
&vertex_label_names,
"Person",
&["full_name".into()],
&["name".into()],
);
assert!(result.is_ok());
let table = vertex_tables.get(&0).unwrap();
let schema = table.schema();
assert!(schema.properties.iter().any(|p| &*p.name == "name"));
}
#[test]
fn test_undo_maintains_vertex_schema_version() {
let mut vertex_tables: HashMap<LabelId, Arc<ShardedVertexTable>> = HashMap::new();
let mut vertex_label_names: HashMap<String, LabelId> = HashMap::new();
let mut vertex_label_counter: LabelId = 0;
create_vertex_type_undo(
&mut vertex_tables,
&mut vertex_label_names,
&mut vertex_label_counter,
"Person",
)
.unwrap();
let table = vertex_tables.get(&0).unwrap();
assert_eq!(table.schema().schema_version, 1);
{
let table = vertex_tables.get_mut(&0).unwrap();
table
.add_property(StoragePropertyDef::new(
"email".into(),
linkrs_core::DataType::String,
))
.unwrap();
}
let table = vertex_tables.get(&0).unwrap();
assert_eq!(table.schema().schema_version, 2);
{
let table = vertex_tables.get_mut(&0).unwrap();
table.rename_property("email", "email_address").unwrap();
}
let table = vertex_tables.get(&0).unwrap();
let version_before_undo = table.schema().schema_version;
assert_eq!(version_before_undo, 3);
let result = TransactionOps::revert_rename_vertex_properties(
&vertex_tables,
&vertex_label_names,
"Person",
&["email_address".into()],
&["email".into()],
);
assert!(result.is_ok());
let table = vertex_tables.get(&0).unwrap();
assert_eq!(table.schema().schema_version, version_before_undo);
assert!(table
.schema()
.properties
.iter()
.any(|p| &*p.name == "email"));
assert!(!table
.schema()
.properties
.iter()
.any(|p| &*p.name == "email_address"));
}
}