use super::CsrWithProperties;
use crate::edge::property_schema::PropertySchema;
use crate::vertex::column::Column;
use linkrs_core::{DataType, StorageError, StorageResult, Value};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
impl CsrWithProperties {
pub fn new(property_schema: Vec<PropertySchema>) -> Self {
let mut property_columns = Vec::with_capacity(property_schema.len());
for schema in &property_schema {
let col = Column::new(
schema.name.to_string(),
schema.prop_id,
schema.data_type.clone(),
schema.nullable,
);
property_columns.push(col);
}
let next_prop_id = property_schema
.iter()
.map(|s| s.prop_id)
.max()
.unwrap_or(-1)
.saturating_add(1)
.max(property_schema.len() as i32);
let mut store = Self {
property_schema,
property_columns,
column_index: HashMap::new(),
prop_id_index: HashMap::new(),
visibility: Vec::new(),
edge_map_segments: Vec::new(),
edge_map_len: 0,
row_to_edge: Vec::new(),
free_list: Vec::new(),
row_count: 0,
dirty_columns: HashSet::new(),
next_prop_id,
history_floor: 0,
inline: false,
};
store.rebuild_schema_indexes();
store
}
pub fn inline_stub(property_schema: Vec<PropertySchema>) -> Self {
let mut store = Self::new(property_schema);
store.inline = true;
store
}
pub fn is_inline_stub(&self) -> bool {
self.inline
}
pub(crate) fn reject_inline(&self) -> StorageResult<()> {
if self.inline {
return Err(StorageError::invalid_operation(
"columnar property access on an inline-form table".to_string(),
));
}
Ok(())
}
pub(crate) fn rebuild_schema_indexes(&mut self) {
self.column_index.clear();
self.prop_id_index.clear();
for (idx, schema) in self.property_schema.iter().enumerate() {
self.column_index.insert(schema.name.clone(), idx);
self.prop_id_index.insert(schema.prop_id, idx);
}
}
pub fn column_name_by_prop_id(&self, prop_id: i32) -> Option<&str> {
self.prop_id_index
.get(&prop_id)
.and_then(|&idx| self.property_schema.get(idx))
.map(|schema| &*schema.name)
}
pub fn clear_dirty_columns(&mut self) {
self.dirty_columns.clear();
}
pub fn property_schema(&self) -> &[PropertySchema] {
&self.property_schema
}
pub fn has_property(&self, name: &str) -> bool {
self.column_index.contains_key(name)
}
pub fn get_property_id(&self, name: &str) -> Option<crate::types::PropertyId> {
self.column_index
.get(name)
.map(|&idx| crate::types::PropertyId::new(self.property_schema[idx].prop_id as u16))
}
pub fn add_property(
&mut self,
name: String,
data_type: DataType,
nullable: bool,
) -> StorageResult<crate::types::PropertyId> {
if self.has_property(&name) {
return Err(StorageError::column_already_exists(name));
}
let prop_id = self.next_prop_id;
self.next_prop_id = self.next_prop_id.saturating_add(1);
let schema =
PropertySchema::new(name.clone(), prop_id, data_type.clone()).nullable(nullable);
self.property_schema.push(schema);
let col = Column::new(name.clone(), prop_id, data_type, nullable);
let rows = self.visibility.len();
if rows > 0 {
col.resize(rows);
}
self.property_columns.push(col);
let idx = self.property_schema.len() - 1;
self.column_index.insert(Arc::from(name.as_str()), idx);
self.mark_column_dirty_at(idx);
Ok(crate::types::PropertyId::new(prop_id as u16))
}
pub fn remove_property(&mut self, name: &str) -> StorageResult<()> {
let idx = self
.column_index
.get(name)
.copied()
.ok_or_else(|| StorageError::column_not_found(name.to_string()))?;
self.property_schema.remove(idx);
self.property_columns.remove(idx);
self.rebuild_schema_indexes();
let mut shifted = HashSet::new();
for pos in self.dirty_columns.drain() {
if pos == idx {
continue;
}
shifted.insert(if pos > idx { pos - 1 } else { pos });
}
self.dirty_columns = shifted;
Ok(())
}
pub fn column_cloned(&self, name: &str) -> Option<crate::vertex::column::Column> {
self.column_index
.get(name)
.and_then(|&idx| self.property_columns.get(idx).cloned())
}
pub fn has_column_dirt(&self, name: &str) -> bool {
self.column_index
.get(name)
.is_some_and(|idx| self.dirty_columns.contains(idx))
}
pub fn dirty_column_names(&self) -> Vec<Arc<str>> {
self.dirty_columns
.iter()
.filter_map(|idx| self.property_schema.get(*idx))
.map(|schema| schema.name.clone())
.collect()
}
pub fn restore_property_at(
&mut self,
index: usize,
schema: PropertySchema,
column: crate::vertex::column::Column,
had_column_dirt: bool,
) {
let at = index.min(self.property_schema.len());
self.property_schema.insert(at, schema);
let at = at.min(self.property_columns.len());
self.property_columns.insert(at, column);
self.rebuild_schema_indexes();
let mut shifted = HashSet::new();
for pos in self.dirty_columns.drain() {
shifted.insert(if pos >= at { pos + 1 } else { pos });
}
self.dirty_columns = shifted;
if had_column_dirt {
self.dirty_columns.insert(at);
}
}
pub fn rename_property(&mut self, old_name: &str, new_name: &str) -> StorageResult<()> {
if self.has_property(new_name) {
return Err(StorageError::column_already_exists(new_name.to_string()));
}
let idx = self
.column_index
.get(old_name)
.copied()
.ok_or_else(|| StorageError::column_not_found(old_name.to_string()))?;
self.property_schema[idx].name = new_name.into();
if let Some(col) = self.property_columns.get_mut(idx) {
col.name = new_name.to_string();
}
self.rebuild_schema_indexes();
Ok(())
}
pub fn backfill_column(&mut self, column: &str, default: &Value) -> StorageResult<()> {
let rows = self.visibility.len();
let idx = self
.column_index
.get(column)
.copied()
.ok_or_else(|| StorageError::column_not_found(column.to_string()))?;
let col = &mut self.property_columns[idx];
for row in 0..rows {
col.set(row, Some(default))?;
}
self.mark_column_dirty_at(idx);
Ok(())
}
pub fn restore_prop_ids(&mut self, ids: &HashMap<Arc<str>, i32>) {
for (idx, schema) in self.property_schema.iter_mut().enumerate() {
if let Some(id) = ids.get(schema.name.as_ref()) {
schema.prop_id = *id;
if let Some(col) = self.property_columns.get_mut(idx) {
col.col_id = *id;
}
}
}
let max_id = self
.property_schema
.iter()
.map(|s| s.prop_id)
.max()
.unwrap_or(-1);
self.next_prop_id = max_id
.saturating_add(1)
.max(self.property_schema.len() as i32);
}
pub fn prop_id_of(&self, name: &str) -> Option<i32> {
self.column_index
.get(name)
.map(|&idx| self.property_schema[idx].prop_id)
}
}