linkrs_storage/edge/csr_with_properties/
schema.rs1use super::CsrWithProperties;
2use crate::edge::property_schema::PropertySchema;
3use crate::vertex::column::Column;
4use linkrs_core::{DataType, StorageError, StorageResult, Value};
5use std::collections::{HashMap, HashSet};
6use std::sync::Arc;
7
8impl CsrWithProperties {
9 pub fn new(property_schema: Vec<PropertySchema>) -> Self {
16 let mut property_columns = Vec::with_capacity(property_schema.len());
17 for schema in &property_schema {
18 let col = Column::new(
19 schema.name.to_string(),
20 schema.prop_id,
21 schema.data_type.clone(),
22 schema.nullable,
23 );
24 property_columns.push(col);
25 }
26 let next_prop_id = property_schema
27 .iter()
28 .map(|s| s.prop_id)
29 .max()
30 .unwrap_or(-1)
31 .saturating_add(1)
32 .max(property_schema.len() as i32);
33 let mut store = Self {
34 property_schema,
35 property_columns,
36 column_index: HashMap::new(),
37 prop_id_index: HashMap::new(),
38 visibility: Vec::new(),
39 edge_map_segments: Vec::new(),
40 edge_map_len: 0,
41 row_to_edge: Vec::new(),
42 free_list: Vec::new(),
43 row_count: 0,
44 dirty_columns: HashSet::new(),
45 next_prop_id,
46 history_floor: 0,
47 inline: false,
48 };
49 store.rebuild_schema_indexes();
50 store
51 }
52
53 pub fn inline_stub(property_schema: Vec<PropertySchema>) -> Self {
60 let mut store = Self::new(property_schema);
61 store.inline = true;
62 store
63 }
64
65 pub fn is_inline_stub(&self) -> bool {
67 self.inline
68 }
69
70 pub(crate) fn reject_inline(&self) -> StorageResult<()> {
71 if self.inline {
72 return Err(StorageError::invalid_operation(
73 "columnar property access on an inline-form table".to_string(),
74 ));
75 }
76 Ok(())
77 }
78
79 pub(crate) fn rebuild_schema_indexes(&mut self) {
81 self.column_index.clear();
82 self.prop_id_index.clear();
83 for (idx, schema) in self.property_schema.iter().enumerate() {
84 self.column_index.insert(schema.name.clone(), idx);
85 self.prop_id_index.insert(schema.prop_id, idx);
86 }
87 }
88
89 pub fn column_name_by_prop_id(&self, prop_id: i32) -> Option<&str> {
91 self.prop_id_index
92 .get(&prop_id)
93 .and_then(|&idx| self.property_schema.get(idx))
94 .map(|schema| &*schema.name)
95 }
96
97 pub fn clear_dirty_columns(&mut self) {
99 self.dirty_columns.clear();
100 }
101
102 pub fn property_schema(&self) -> &[PropertySchema] {
103 &self.property_schema
104 }
105
106 pub fn has_property(&self, name: &str) -> bool {
107 self.column_index.contains_key(name)
108 }
109
110 pub fn get_property_id(&self, name: &str) -> Option<crate::types::PropertyId> {
111 self.column_index
112 .get(name)
113 .map(|&idx| crate::types::PropertyId::new(self.property_schema[idx].prop_id as u16))
114 }
115
116 pub fn add_property(
117 &mut self,
118 name: String,
119 data_type: DataType,
120 nullable: bool,
121 ) -> StorageResult<crate::types::PropertyId> {
122 if self.has_property(&name) {
123 return Err(StorageError::column_already_exists(name));
124 }
125 let prop_id = self.next_prop_id;
126 self.next_prop_id = self.next_prop_id.saturating_add(1);
127 let schema =
128 PropertySchema::new(name.clone(), prop_id, data_type.clone()).nullable(nullable);
129 self.property_schema.push(schema);
130 let col = Column::new(name.clone(), prop_id, data_type, nullable);
131 let rows = self.visibility.len();
132 if rows > 0 {
133 col.resize(rows);
134 }
135 self.property_columns.push(col);
136 let idx = self.property_schema.len() - 1;
137 self.column_index.insert(Arc::from(name.as_str()), idx);
138 self.mark_column_dirty_at(idx);
139 Ok(crate::types::PropertyId::new(prop_id as u16))
140 }
141
142 pub fn remove_property(&mut self, name: &str) -> StorageResult<()> {
143 let idx = self
144 .column_index
145 .get(name)
146 .copied()
147 .ok_or_else(|| StorageError::column_not_found(name.to_string()))?;
148 self.property_schema.remove(idx);
149 self.property_columns.remove(idx);
150 self.rebuild_schema_indexes();
151 let mut shifted = HashSet::new();
154 for pos in self.dirty_columns.drain() {
155 if pos == idx {
156 continue;
157 }
158 shifted.insert(if pos > idx { pos - 1 } else { pos });
159 }
160 self.dirty_columns = shifted;
161 Ok(())
162 }
163
164 pub fn column_cloned(&self, name: &str) -> Option<crate::vertex::column::Column> {
166 self.column_index
167 .get(name)
168 .and_then(|&idx| self.property_columns.get(idx).cloned())
169 }
170
171 pub fn has_column_dirt(&self, name: &str) -> bool {
173 self.column_index
174 .get(name)
175 .is_some_and(|idx| self.dirty_columns.contains(idx))
176 }
177
178 pub fn dirty_column_names(&self) -> Vec<Arc<str>> {
182 self.dirty_columns
183 .iter()
184 .filter_map(|idx| self.property_schema.get(*idx))
185 .map(|schema| schema.name.clone())
186 .collect()
187 }
188
189 pub fn restore_property_at(
192 &mut self,
193 index: usize,
194 schema: PropertySchema,
195 column: crate::vertex::column::Column,
196 had_column_dirt: bool,
197 ) {
198 let at = index.min(self.property_schema.len());
199 self.property_schema.insert(at, schema);
200 let at = at.min(self.property_columns.len());
201 self.property_columns.insert(at, column);
202 self.rebuild_schema_indexes();
203 let mut shifted = HashSet::new();
206 for pos in self.dirty_columns.drain() {
207 shifted.insert(if pos >= at { pos + 1 } else { pos });
208 }
209 self.dirty_columns = shifted;
210 if had_column_dirt {
211 self.dirty_columns.insert(at);
212 }
213 }
214
215 pub fn rename_property(&mut self, old_name: &str, new_name: &str) -> StorageResult<()> {
216 if self.has_property(new_name) {
217 return Err(StorageError::column_already_exists(new_name.to_string()));
218 }
219 let idx = self
220 .column_index
221 .get(old_name)
222 .copied()
223 .ok_or_else(|| StorageError::column_not_found(old_name.to_string()))?;
224 self.property_schema[idx].name = new_name.into();
225 if let Some(col) = self.property_columns.get_mut(idx) {
228 col.name = new_name.to_string();
229 }
230 self.rebuild_schema_indexes();
232 Ok(())
233 }
234
235 pub fn backfill_column(&mut self, column: &str, default: &Value) -> StorageResult<()> {
240 let rows = self.visibility.len();
241 let idx = self
242 .column_index
243 .get(column)
244 .copied()
245 .ok_or_else(|| StorageError::column_not_found(column.to_string()))?;
246 let col = &mut self.property_columns[idx];
247 for row in 0..rows {
248 col.set(row, Some(default))?;
249 }
250 self.mark_column_dirty_at(idx);
251 Ok(())
252 }
253
254 pub fn restore_prop_ids(&mut self, ids: &HashMap<Arc<str>, i32>) {
261 for (idx, schema) in self.property_schema.iter_mut().enumerate() {
262 if let Some(id) = ids.get(schema.name.as_ref()) {
263 schema.prop_id = *id;
264 if let Some(col) = self.property_columns.get_mut(idx) {
265 col.col_id = *id;
266 }
267 }
268 }
269 let max_id = self
270 .property_schema
271 .iter()
272 .map(|s| s.prop_id)
273 .max()
274 .unwrap_or(-1);
275 self.next_prop_id = max_id
276 .saturating_add(1)
277 .max(self.property_schema.len() as i32);
278 }
279
280 pub fn prop_id_of(&self, name: &str) -> Option<i32> {
282 self.column_index
283 .get(name)
284 .map(|&idx| self.property_schema[idx].prop_id)
285 }
286}