use super::*;
#[test]
fn test_serial_auto_allocates_when_column_missing() {
let mut storage = create_test_storage();
setup_space(&mut storage);
setup_serial_person_tag(&mut storage);
insert_serial_vertex(&mut storage, 101, "Alice");
insert_serial_vertex(&mut storage, 102, "Bob");
let alice = storage
.get_vertex(
"test_space",
"Person",
&VertexId::try_from_int64(101).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(alice.property_value("id"), Some(Value::BigInt(1)));
let bob = storage
.get_vertex(
"test_space",
"Person",
&VertexId::try_from_int64(102).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(bob.property_value("id"), Some(Value::BigInt(2)));
}
#[test]
fn test_serial_explicit_value_advances_counter() {
let mut storage = create_test_storage();
setup_space(&mut storage);
setup_serial_person_tag(&mut storage);
let explicit = Vertex::new(
VertexId::try_from_int64(101).expect("test vertex id"),
Tag::new(
"Person".to_string(),
vec![
("id".into(), Value::BigInt(5)),
("name".into(), Value::string("Alice")),
]
.into_iter()
.collect(),
),
);
storage.insert_vertex("test_space", explicit).unwrap();
insert_serial_vertex(&mut storage, 102, "Bob");
let bob = storage
.get_vertex(
"test_space",
"Person",
&VertexId::try_from_int64(102).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(bob.property_value("id"), Some(Value::BigInt(6)));
}
#[test]
fn test_serial_explicit_duplicate_is_rejected() {
let mut storage = create_test_storage();
setup_space(&mut storage);
setup_serial_person_tag(&mut storage);
insert_serial_vertex(&mut storage, 101, "Alice");
let duplicate = Vertex::new(
VertexId::try_from_int64(102).expect("test vertex id"),
Tag::new(
"Person".to_string(),
vec![
("id".into(), Value::BigInt(1)),
("name".into(), Value::string("Bob")),
]
.into_iter()
.collect(),
),
);
let error = storage.insert_vertex("test_space", duplicate).unwrap_err();
assert!(
error.to_string().contains("SERIAL"),
"unexpected error: {}",
error
);
}
#[test]
fn test_serial_allocates_per_tag_and_per_space() {
let mut storage = create_test_storage();
setup_space(&mut storage);
setup_serial_person_tag(&mut storage);
let city = linkrs_core::types::TagInfo::new("City".to_string()).with_properties(vec![
PropertyDef::new("vid".into(), DataType::BigInt),
PropertyDef::new("id".into(), DataType::BigInt).with_serial(true),
PropertyDef::new("name".into(), DataType::String),
]);
storage
.create_tag("test_space", &city)
.expect("Failed to create City tag");
let mut second_space =
SpaceInfo::new("second_space".to_string()).with_vid_type(DataType::BigInt);
storage.create_space(&mut second_space).unwrap();
let second_person =
linkrs_core::types::TagInfo::new("Person".to_string()).with_properties(vec![
PropertyDef::new("vid".into(), DataType::BigInt),
PropertyDef::new("id".into(), DataType::BigInt).with_serial(true),
PropertyDef::new("name".into(), DataType::String),
]);
storage
.create_tag("second_space", &second_person)
.expect("Failed to create second-space Person tag");
insert_serial_vertex(&mut storage, 101, "Alice");
let vertex = Vertex::new(
VertexId::try_from_int64(101).expect("test vertex id"),
Tag::new(
"Person".to_string(),
vec![("name".into(), Value::string("Bob"))]
.into_iter()
.collect(),
),
);
storage.insert_vertex("second_space", vertex).unwrap();
let city_vertex = Vertex::new(
VertexId::try_from_int64(201).expect("test vertex id"),
Tag::new(
"City".to_string(),
vec![("name".into(), Value::string("Paris"))]
.into_iter()
.collect(),
),
);
storage.insert_vertex("test_space", city_vertex).unwrap();
let alice = storage
.get_vertex(
"test_space",
"Person",
&VertexId::try_from_int64(101).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(alice.property_value("id"), Some(Value::BigInt(1)));
let bob = storage
.get_vertex(
"second_space",
"Person",
&VertexId::try_from_int64(101).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(bob.property_value("id"), Some(Value::BigInt(1)));
let paris = storage
.get_vertex(
"test_space",
"City",
&VertexId::try_from_int64(201).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(paris.property_value("id"), Some(Value::BigInt(1)));
}
#[test]
fn test_serial_edge_type_auto_allocates() {
let mut storage = create_test_storage();
setup_space(&mut storage);
setup_person_tag(&mut storage);
let edge_type =
linkrs_core::types::EdgeTypeInfo::new("KNOWS".to_string()).with_properties(vec![
PropertyDef::new("seq".into(), DataType::BigInt).with_serial(true),
]);
storage
.create_edge_type("test_space", &edge_type)
.expect("Failed to create edge type");
insert_test_vertex(&mut storage, 1, "Alice");
insert_test_vertex(&mut storage, 2, "Bob");
let edge = Edge::new(
VertexId::try_from_int64(1).expect("test vertex id"),
VertexId::try_from_int64(2).expect("test vertex id"),
"KNOWS".to_string(),
0,
std::collections::HashMap::new(),
);
storage.insert_edge("test_space", edge).unwrap();
let retrieved = storage
.get_edge(
"test_space",
&VertexId::try_from_int64(1).expect("test vertex id"),
&VertexId::try_from_int64(2).expect("test vertex id"),
"KNOWS",
0,
)
.unwrap()
.unwrap();
assert_eq!(
retrieved.props.get("seq"),
Some(&Value::BigInt(1)),
"edge serial column must be auto-allocated"
);
}
#[test]
fn test_serial_validation_rejects_default_and_multiple_columns() {
let mut storage = create_test_storage();
setup_space(&mut storage);
let with_default =
linkrs_core::types::TagInfo::new("BadDefault".to_string()).with_properties(vec![
PropertyDef::new("id".into(), DataType::BigInt)
.with_serial(true)
.with_default(Some(Value::BigInt(1))),
]);
let error = storage
.create_tag("test_space", &with_default)
.expect_err("SERIAL with DEFAULT must be rejected");
assert!(error.to_string().contains("DEFAULT"));
let two_serials =
linkrs_core::types::TagInfo::new("BadMultiple".to_string()).with_properties(vec![
PropertyDef::new("id".into(), DataType::BigInt).with_serial(true),
PropertyDef::new("seq".into(), DataType::BigInt).with_serial(true),
]);
let error = storage
.create_tag("test_space", &two_serials)
.expect_err("two SERIAL columns must be rejected");
assert!(error.to_string().contains("only one SERIAL"));
}
#[test]
fn test_serial_survives_save_load_round_trip() {
let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
let mut storage = GraphStorage::new_with_path(temp_dir.path().to_path_buf())
.expect("Failed to create persistent storage");
{
let mut space = SpaceInfo::new("test_space".to_string()).with_vid_type(DataType::BigInt);
storage.create_space(&mut space).unwrap();
setup_serial_person_tag(&mut storage);
insert_serial_vertex(&mut storage, 101, "Alice");
insert_serial_vertex(&mut storage, 102, "Bob");
}
storage.save_to_disk().expect("save to disk");
drop(storage);
let mut reloaded =
GraphStorage::new_with_path(temp_dir.path().to_path_buf()).expect("reload storage");
reloaded.load_from_disk().expect("load from disk");
insert_serial_vertex(&mut reloaded, 103, "Carol");
let carol = reloaded
.get_vertex(
"test_space",
"Person",
&VertexId::try_from_int64(103).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(carol.property_value("id"), Some(Value::BigInt(3)));
reloaded
.delete_vertex(
"test_space",
"Person",
&VertexId::try_from_int64(102).expect("test vertex id"),
)
.expect("delete vertex");
reloaded.save_to_disk().expect("save to disk");
drop(reloaded);
let mut reloaded2 =
GraphStorage::new_with_path(temp_dir.path().to_path_buf()).expect("reload storage");
reloaded2.load_from_disk().expect("load from disk");
insert_serial_vertex(&mut reloaded2, 104, "Dave");
let dave = reloaded2
.get_vertex(
"test_space",
"Person",
&VertexId::try_from_int64(104).expect("test vertex id"),
)
.unwrap()
.unwrap();
assert_eq!(
dave.property_value("id"),
Some(Value::BigInt(4)),
"counter must not fall back after row deletion"
);
}