pub mod column;
pub mod gc_manager;
pub mod id_indexer;
pub mod vertex_table;
pub mod vertex_timestamp;
pub mod write_scope;
pub(crate) mod latch_order {
pub(crate) const RANK_IDENTITY: u8 = 1;
pub(crate) const RANK_SEGMENT: u8 = 2;
#[cfg(debug_assertions)]
thread_local! {
static HELD: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
}
pub(crate) struct Guard<G> {
inner: G,
rank: u8,
}
impl<G> Guard<G> {
pub(crate) fn claim(inner: G, rank: u8, site: &'static str) -> Self {
acquire(rank, site);
Self { inner, rank }
}
}
impl<G: std::ops::Deref> std::ops::Deref for Guard<G> {
type Target = G::Target;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<G: std::ops::DerefMut> std::ops::DerefMut for Guard<G> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<G> Drop for Guard<G> {
#[inline]
fn drop(&mut self) {
release(self.rank);
}
}
#[cfg(debug_assertions)]
fn acquire(rank: u8, site: &'static str) {
HELD.with(|held| {
let mut held = held.borrow_mut();
if let Some(&top) = held.last() {
assert!(
rank >= top,
"latch order violation: acquiring {site} (rank {rank}) while holding a higher-ranked latch (top rank {top})"
);
}
held.push(rank);
});
}
#[cfg(not(debug_assertions))]
#[inline]
fn acquire(_rank: u8, _site: &'static str) {}
#[cfg(debug_assertions)]
fn release(rank: u8) {
HELD.with(|held| {
let popped = held.borrow_mut().pop();
debug_assert_eq!(
popped,
Some(rank),
"latch claims must release in LIFO order"
);
});
}
#[cfg(not(debug_assertions))]
#[inline]
fn release(_rank: u8) {}
}
pub use column as column_store;
use crate::types::StoragePropertyDef;
pub use column::ColumnStore;
pub use gc_manager::VertexGcConfig;
pub use id_indexer::{IdIndexer, IdKey, PkLookup};
pub use vertex_table::ShardedVertexTable;
pub use vertex_timestamp::VertexTimestamp;
pub use write_scope::{WriteScope, MAX_WRITE_SCOPE_KEYS};
use linkrs_core::{DataType, StorageError, StorageResult, Value};
pub use linkrs_core::types::{LabelId, Timestamp, VertexId, INVALID_TIMESTAMP, MAX_TIMESTAMP};
#[derive(Debug, Clone)]
pub struct VertexRecord {
pub vid: VertexId,
pub internal_id: u32,
pub properties: Vec<(std::sync::Arc<str>, Value)>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct VertexSchema {
pub label_id: LabelId,
pub label_name: String,
pub properties: Vec<StoragePropertyDef>,
pub primary_key_index: usize,
pub schema_version: u64,
}
impl VertexSchema {
pub fn validate_on_creation(&self) -> Result<(), String> {
if self.properties.is_empty() {
return Err("Schema must have at least one property".to_string());
}
if self.primary_key_index >= self.properties.len() {
return Err(format!(
"Invalid primary key index: {} >= property count {}",
self.primary_key_index,
self.properties.len()
));
}
let primary_key_prop = &self.properties[self.primary_key_index];
Self::validate_key_type(&primary_key_prop.data_type, &primary_key_prop.name)?;
if primary_key_prop.name.is_empty() {
return Err("Primary key name cannot be empty".to_string());
}
Self::validate_identifier(&primary_key_prop.name)?;
let mut seen_names = std::collections::HashSet::new();
for prop in &self.properties {
if !seen_names.insert(&prop.name) {
return Err(format!("Duplicate property name: '{}'", prop.name));
}
if prop.name.is_empty() {
return Err("Property name cannot be empty".to_string());
}
Self::validate_identifier(&prop.name)?;
Self::validate_property_type(&prop.data_type, &prop.name)?;
}
Ok(())
}
fn validate_identifier(name: &str) -> Result<(), String> {
let first_char = match name.chars().next() {
Some(c) => c,
None => return Err("Identifier cannot be empty".to_string()),
};
if !first_char.is_ascii_alphabetic() && first_char != '_' {
return Err(format!(
"Identifier '{}' must start with ASCII letter or underscore, got '{}'",
name, first_char
));
}
for (i, c) in name.chars().enumerate() {
if !c.is_ascii_alphanumeric() && c != '_' {
return Err(format!(
"Identifier '{}' contains invalid character '{}' at position {}. \
Only ASCII letters, digits, and underscores are allowed.",
name, c, i
));
}
}
Ok(())
}
fn validate_key_type(data_type: &linkrs_core::DataType, prop_name: &str) -> Result<(), String> {
use linkrs_core::DataType;
let allowed = matches!(
data_type,
DataType::SmallInt
| DataType::Int
| DataType::BigInt
| DataType::String
| DataType::FixedString(_)
);
if !allowed {
return Err(format!(
"Primary key '{}' has invalid type '{:?}'. \
The primary key column mirrors the vertex id, so only \
SmallInt, Int, BigInt, String and FixedString are allowed",
prop_name, data_type
));
}
Ok(())
}
fn validate_property_type(
data_type: &linkrs_core::DataType,
prop_name: &str,
) -> Result<(), String> {
use linkrs_core::DataType;
match data_type {
DataType::Empty => Err(format!(
"Property '{}' cannot have type Empty - properties must have valid types",
prop_name
)),
DataType::Null => Err(format!(
"Property '{}' cannot have type Null - use nullable=true instead",
prop_name
)),
_ => Ok(()),
}
}
}
pub(crate) fn primary_key_mirror_value(data_type: &DataType, key: &IdKey) -> StorageResult<Value> {
let out_of_range = |detail: String| StorageError::invalid_input(detail);
let int_mirror = |id: i64| -> StorageResult<Value> {
match data_type {
DataType::SmallInt => i16::try_from(id).map(Value::SmallInt).map_err(|_| {
out_of_range(format!("Vertex id {} overflows SmallInt primary key", id))
}),
DataType::Int => i32::try_from(id)
.map(Value::Int)
.map_err(|_| out_of_range(format!("Vertex id {} overflows Int primary key", id))),
DataType::BigInt => Ok(Value::BigInt(id)),
DataType::String | DataType::FixedString(_) => Ok(Value::string(id.to_string())),
_ => Err(out_of_range(format!(
"Primary key type {:?} cannot mirror a vertex id",
data_type
))),
}
};
match key {
IdKey::Int(id) => int_mirror(*id),
IdKey::Text(text) => match data_type {
DataType::String | DataType::FixedString(_) => Ok(Value::string(text.clone())),
DataType::SmallInt | DataType::Int | DataType::BigInt => text
.parse::<i64>()
.map_err(|_| {
out_of_range(format!(
"Text vertex id {:?} cannot mirror integer primary key",
text
))
})
.and_then(int_mirror),
_ => Err(out_of_range(format!(
"Primary key type {:?} cannot mirror a vertex id",
data_type
))),
},
}
}