Skip to main content

linkrs_api/api_core/
schema_api.rs

1//! Schema Manipulation API - Core Layer
2//!
3//! Provides transport layer-independent Schema management capabilities
4
5use crate::api_core::{CoreError, CoreResult, IndexTarget, PropertyDef, SpaceConfig};
6use linkrs_core::types::{
7    EdgeTypeInfo, Index, IndexField, IndexStatus, IndexType, SpaceInfo, TagInfo,
8};
9use linkrs_storage::StorageClient;
10use parking_lot::RwLock;
11use std::sync::Arc;
12
13/// Schema Manipulation API - Core Layer
14pub struct SchemaApi<S: StorageClient> {
15    storage: Arc<RwLock<S>>,
16}
17
18impl<S: StorageClient> SchemaApi<S> {
19    /// Creating a New Schema API Instance
20    pub fn new(storage: Arc<RwLock<S>>) -> Self {
21        Self { storage }
22    }
23
24    /// Creating a graph space
25    ///
26    /// # Parameters
27    /// - `name': name of the space
28    /// - `config`: space configuration
29    pub fn create_space(&self, name: &str, config: SpaceConfig) -> CoreResult<()> {
30        let mut space_info = SpaceInfo::new(name.to_string())
31            .with_vid_type(config.vid_type)
32            .with_comment(config.comment);
33
34        let mut storage = self.storage.write();
35        storage
36            .create_space(&mut space_info)
37            .map_err(|e| CoreError::StorageError(e.to_string()))?;
38
39        log::info!("Created graph space successfully: {}", name);
40        Ok(())
41    }
42
43    /// Deletion of map space
44    ///
45    /// # Parameters
46    /// - `name`: space name
47    pub fn drop_space(&self, name: &str) -> CoreResult<()> {
48        let mut storage = self.storage.write();
49        let result = storage
50            .drop_space(name)
51            .map_err(|e| CoreError::StorageError(e.to_string()))?;
52
53        if result {
54            log::info!("Deleted graph space successfully: {}", name);
55            Ok(())
56        } else {
57            Err(CoreError::NotFound(format!(
58                "Graph space '{}' does not exist",
59                name
60            )))
61        }
62    }
63
64    /// Use of map space
65    ///
66    /// # Parameters
67    /// - `name`: space name
68    ///
69    /// # Return
70    /// Space ID
71    pub fn use_space(&self, name: &str) -> CoreResult<u64> {
72        let storage = self.storage.write();
73        let space_id = storage
74            .get_space_id(name)
75            .map_err(|e| CoreError::StorageError(e.to_string()))?;
76
77        log::info!("Using graph space: {} (ID: {})", name, space_id);
78        Ok(space_id)
79    }
80
81    /// List all graph spaces
82    ///
83    /// # Returns
84    /// List of space information
85    pub fn list_spaces(&self) -> CoreResult<Vec<linkrs_core::types::SpaceInfo>> {
86        let storage = self.storage.write();
87        let spaces = storage
88            .list_spaces()
89            .map_err(|e| CoreError::StorageError(e.to_string()))?;
90
91        log::info!("Listed {} graph spaces", spaces.len());
92        Ok(spaces)
93    }
94
95    /// Creating Tags
96    ///
97    /// # Parameters
98    /// `space_id`: Space ID
99    /// - `name`: label name
100    /// - `properties`: list of property definitions
101    pub fn create_tag(
102        &self,
103        space_id: u64,
104        name: &str,
105        properties: Vec<PropertyDef>,
106    ) -> CoreResult<()> {
107        // Get space name
108        let space_name = self.get_space_name_by_id(space_id)?;
109
110        // Conversion Attribute Definition
111        let core_properties: Vec<linkrs_core::types::PropertyDef> =
112            properties.into_iter().map(|p| p.into()).collect();
113
114        let tag_info = TagInfo::new(name.to_string()).with_properties(core_properties);
115
116        let mut storage = self.storage.write();
117        let result = storage
118            .create_tag(&space_name, &tag_info)
119            .map_err(|e| CoreError::StorageError(e.to_string()))?;
120
121        log::info!(
122            "Created tag successfully: {} in space {} with id {}",
123            name,
124            space_id,
125            result
126        );
127        Ok(())
128    }
129
130    /// Delete Tags
131    ///
132    /// # Parameters
133    /// - `space_id`: Space ID
134    /// - `name`: tag name
135    pub fn drop_tag(&self, space_id: u64, name: &str) -> CoreResult<()> {
136        let space_name = self.get_space_name_by_id(space_id)?;
137
138        let mut storage = self.storage.write();
139        let result = storage
140            .drop_tag(&space_name, name)
141            .map_err(|e| CoreError::StorageError(e.to_string()))?;
142
143        if result {
144            log::info!("Deleted tag successfully: {} from space {}", name, space_id);
145            Ok(())
146        } else {
147            Err(CoreError::NotFound(format!(
148                "Tag '{}' does not exist",
149                name
150            )))
151        }
152    }
153
154    /// Alter Tag
155    ///
156    /// # Parameters
157    /// - `space_id`: Space ID
158    /// - `tag_name`: tag name
159    /// - `additions`: list of properties to add
160    /// - `deletions`: list of property names to delete
161    pub fn alter_tag(
162        &self,
163        space_id: u64,
164        tag_name: &str,
165        additions: Vec<PropertyDef>,
166        deletions: Vec<String>,
167    ) -> CoreResult<()> {
168        let space_name = self.get_space_name_by_id(space_id)?;
169
170        // Convert PropertyDef to core PropertyDef
171        let core_additions: Vec<linkrs_core::types::PropertyDef> =
172            additions.into_iter().map(|p| p.into()).collect();
173
174        let mut storage = self.storage.write();
175        let result = storage
176            .alter_tag(&space_name, tag_name, core_additions, deletions, None)
177            .map_err(|e| CoreError::StorageError(e.to_string()))?;
178
179        if result {
180            log::info!(
181                "Altered tag successfully: {} in space {}",
182                tag_name,
183                space_id
184            );
185            Ok(())
186        } else {
187            Err(CoreError::NotFound(format!(
188                "Tag '{}' does not exist or no changes made",
189                tag_name
190            )))
191        }
192    }
193
194    /// Creating Edge Types
195    ///
196    /// # Parameters
197    /// - `space_id`: space ID
198    /// - `name`: name of edge type
199    /// - `properties`: list of property definitions
200    pub fn create_edge_type(
201        &self,
202        space_id: u64,
203        name: &str,
204        properties: Vec<PropertyDef>,
205    ) -> CoreResult<()> {
206        let space_name = self.get_space_name_by_id(space_id)?;
207
208        // Conversion Attribute Definition
209        let core_properties: Vec<linkrs_core::types::PropertyDef> =
210            properties.into_iter().map(|p| p.into()).collect();
211
212        let edge_type_info = EdgeTypeInfo::new(name.to_string()).with_properties(core_properties);
213
214        let mut storage = self.storage.write();
215        let result = storage
216            .create_edge_type(&space_name, &edge_type_info)
217            .map_err(|e| CoreError::StorageError(e.to_string()))?;
218
219        log::info!(
220            "Created edge type successfully: {} in space {} with id {}",
221            name,
222            space_id,
223            result
224        );
225        Ok(())
226    }
227
228    /// Delete Edge Type
229    ///
230    /// # Parameters
231    /// - `space_id`: Space ID
232    /// - `name`: name of edge type
233    pub fn drop_edge_type(&self, space_id: u64, name: &str) -> CoreResult<()> {
234        let space_name = self.get_space_name_by_id(space_id)?;
235
236        let mut storage = self.storage.write();
237        let result = storage
238            .drop_edge_type(&space_name, name)
239            .map_err(|e| CoreError::StorageError(e.to_string()))?;
240
241        if result {
242            log::info!(
243                "Deleted edge type successfully: {} from space {}",
244                name,
245                space_id
246            );
247            Ok(())
248        } else {
249            Err(CoreError::NotFound(format!(
250                "Edge type '{}' does not exist",
251                name
252            )))
253        }
254    }
255
256    /// Alter Edge Type
257    ///
258    /// # Parameters
259    /// - `space_id`: Space ID
260    /// - `edge_type_name`: edge type name
261    /// - `additions`: list of properties to add
262    /// - `deletions`: list of property names to delete
263    pub fn alter_edge_type(
264        &self,
265        space_id: u64,
266        edge_type_name: &str,
267        additions: Vec<PropertyDef>,
268        deletions: Vec<String>,
269    ) -> CoreResult<()> {
270        let space_name = self.get_space_name_by_id(space_id)?;
271
272        // Convert PropertyDef to core PropertyDef
273        let core_additions: Vec<linkrs_core::types::PropertyDef> =
274            additions.into_iter().map(|p| p.into()).collect();
275
276        let mut storage = self.storage.write();
277        let result = storage
278            .alter_edge_type(&space_name, edge_type_name, core_additions, deletions, None)
279            .map_err(|e| CoreError::StorageError(e.to_string()))?;
280
281        if result {
282            log::info!(
283                "Altered edge type successfully: {} in space {}",
284                edge_type_name,
285                space_id
286            );
287            Ok(())
288        } else {
289            Err(CoreError::NotFound(format!(
290                "Edge type '{}' does not exist or no changes made",
291                edge_type_name
292            )))
293        }
294    }
295
296    /// Creating Indexes
297    ///
298    /// # Parameters
299    /// - `space_id`: Space ID
300    /// - **Name**: Index name
301    /// - `target`: index target (label or edge type)
302    pub fn create_index(&self, space_id: u64, name: &str, target: IndexTarget) -> CoreResult<()> {
303        let space_name = self.get_space_name_by_id(space_id)?;
304
305        // Build indexes based on target type
306        let (schema_name, fields, index_type) = match target {
307            IndexTarget::Tag {
308                name: tag_name,
309                fields,
310            } => {
311                // Get label information to determine field type
312                let storage = self.storage.read();
313                let tag_info = storage
314                    .get_tag(&space_name, &tag_name)
315                    .map_err(|e| CoreError::StorageError(e.to_string()))?;
316
317                let tag_info = tag_info.ok_or_else(|| {
318                    CoreError::NotFound(format!("Tag '{}' does not exist", tag_name))
319                })?;
320
321                // Building Index Fields
322                let index_fields = self.build_index_fields(&fields, &tag_info.properties)?;
323                (tag_name, index_fields, IndexType::TagIndex)
324            }
325            IndexTarget::Edge {
326                name: edge_name,
327                fields,
328            } => {
329                // Get edge type information to determine field type
330                let storage = self.storage.read();
331                let edge_info = storage
332                    .get_edge_type(&space_name, &edge_name)
333                    .map_err(|e| CoreError::StorageError(e.to_string()))?;
334
335                let edge_info = edge_info.ok_or_else(|| {
336                    CoreError::NotFound(format!("Edge type '{}' does not exist", edge_name))
337                })?;
338
339                // Building Index Fields
340                let index_fields = self.build_index_fields(&fields, &edge_info.properties)?;
341                (edge_name, index_fields, IndexType::EdgeIndex)
342            }
343        };
344
345        // Call the corresponding creation method based on the index type
346        let mut storage = self.storage.write();
347        let result = match index_type {
348            IndexType::TagIndex => {
349                let index = Index {
350                    id: 0, // Allocated by the storage layer
351                    name: name.to_string(),
352                    space_id,
353                    schema_name,
354                    fields,
355                    properties: Vec::new(),
356                    index_type: IndexType::TagIndex,
357                    status: IndexStatus::Active,
358                    is_unique: false,
359                    comment: None,
360                    covering: false,
361                    partial_condition: None,
362                };
363                storage.create_tag_index(&space_name, &index)
364            }
365            IndexType::EdgeIndex => {
366                let index = Index {
367                    id: 0, // Allocated by the storage layer
368                    name: name.to_string(),
369                    space_id,
370                    schema_name,
371                    fields,
372                    properties: Vec::new(),
373                    index_type: IndexType::EdgeIndex,
374                    status: IndexStatus::Active,
375                    is_unique: false,
376                    comment: None,
377                    covering: false,
378                    partial_condition: None,
379                };
380                storage.create_edge_index(&space_name, &index)
381            }
382        }
383        .map_err(|e| CoreError::StorageError(e.to_string()))?;
384
385        if result {
386            log::info!(
387                "Created index successfully: {} in space {:?}",
388                name,
389                space_id
390            );
391            Ok(())
392        } else {
393            Err(CoreError::SchemaOperationFailed(format!(
394                "Failed to create index '{}'",
395                name
396            )))
397        }
398    }
399
400    /// Delete the index.
401    ///
402    /// # Parameters
403    /// - `space_id`: Space ID
404    /// - `name`: index name
405    pub fn drop_index(&self, space_id: u64, name: &str) -> CoreResult<()> {
406        let space_name = self.get_space_name_by_id(space_id)?;
407
408        let mut storage = self.storage.write();
409
410        // Try to delete the tag index.
411        if let Ok(Some(_)) = storage.get_tag_index(&space_name, name) {
412            let result = storage
413                .drop_tag_index(&space_name, name)
414                .map_err(|e| CoreError::StorageError(e.to_string()))?;
415            if result {
416                log::info!(
417                    "Deleted tag index successfully: {} from space {}",
418                    name,
419                    space_id
420                );
421                return Ok(());
422            }
423        }
424
425        // Fall back to edge indexes, which share the same name scope.
426        if let Ok(Some(_)) = storage.get_edge_index(&space_name, name) {
427            let result = storage
428                .drop_edge_index(&space_name, name)
429                .map_err(|e| CoreError::StorageError(e.to_string()))?;
430            if result {
431                log::info!(
432                    "Deleted edge index successfully: {} from space {}",
433                    name,
434                    space_id
435                );
436                return Ok(());
437            }
438        }
439
440        Err(CoreError::NotFound(format!(
441            "Index '{}' does not exist",
442            name
443        )))
444    }
445
446    /// Rebuild Index
447    ///
448    /// # Parameters
449    /// - `space_id`: Space ID
450    /// - `index_name`: index name
451    pub fn rebuild_index(&self, space_id: u64, index_name: &str) -> CoreResult<()> {
452        let space_name = self.get_space_name_by_id(space_id)?;
453
454        let mut storage = self.storage.write();
455
456        // Try to rebuild tag index
457        if let Ok(Some(_)) = storage.get_tag_index(&space_name, index_name) {
458            let result = storage
459                .rebuild_tag_index(&space_name, index_name)
460                .map_err(|e| CoreError::StorageError(e.to_string()))?;
461            if result {
462                log::info!(
463                    "Rebuilt tag index successfully: {} in space {}",
464                    index_name,
465                    space_id
466                );
467                return Ok(());
468            }
469        }
470
471        // Fall back to edge indexes, which share the same name scope.
472        if let Ok(Some(_)) = storage.get_edge_index(&space_name, index_name) {
473            let result = storage
474                .rebuild_edge_index(&space_name, index_name)
475                .map_err(|e| CoreError::StorageError(e.to_string()))?;
476            if result {
477                log::info!(
478                    "Rebuilt edge index successfully: {} in space {}",
479                    index_name,
480                    space_id
481                );
482                return Ok(());
483            }
484        }
485
486        Err(CoreError::NotFound(format!(
487            "Index '{}' does not exist",
488            index_name
489        )))
490    }
491}
492
493// Internal auxiliary methods
494impl<S: StorageClient> SchemaApi<S> {
495    /// Retrieve the space name based on the space ID.
496    fn get_space_name_by_id(&self, space_id: u64) -> CoreResult<String> {
497        let storage = self.storage.read();
498        let space_info = storage
499            .get_space_by_id(space_id)
500            .map_err(|e| CoreError::StorageError(e.to_string()))?
501            .ok_or_else(|| CoreError::NotFound(format!("Space ID {} does not exist", space_id)))?;
502        Ok(space_info.space_name)
503    }
504
505    /// Construct a list of index fields
506    fn build_index_fields(
507        &self,
508        field_names: &[String],
509        properties: &[linkrs_core::types::PropertyDef],
510    ) -> CoreResult<Vec<IndexField>> {
511        let mut fields = Vec::new();
512
513        for field_name in field_names {
514            let prop = properties
515                .iter()
516                .find(|p| &p.name == field_name)
517                .ok_or_else(|| {
518                    CoreError::InvalidParameter(format!("Field '{}' does not exist", field_name))
519                })?;
520
521            // Create the corresponding Value type for the IndexField.
522            let value_type = Self::datatype_to_value(&prop.data_type);
523
524            fields.push(IndexField::new(
525                field_name.clone(),
526                value_type,
527                prop.nullable,
528            ));
529        }
530
531        Ok(fields)
532    }
533
534    /// Convert the DataType to a Value (for use with index field types).
535    fn datatype_to_value(data_type: &linkrs_core::DataType) -> linkrs_core::Value {
536        use linkrs_core::value::date_time::{DateTimeValue, DateValue, TimeValue};
537        use linkrs_core::value::NullType;
538        use linkrs_core::DataType;
539        use linkrs_core::Value;
540
541        match data_type {
542            DataType::SmallInt => Value::SmallInt(0),
543            DataType::Int => Value::Int(0),
544            DataType::BigInt => Value::BigInt(0),
545            DataType::Float => Value::Float(0.0),
546            DataType::Double => Value::Double(0.0),
547            DataType::String | DataType::FixedString(_) => Value::string(""),
548            DataType::Bool => Value::Bool(false),
549            DataType::Date => Value::Date(DateValue {
550                year: 1970,
551                month: 1,
552                day: 1,
553            }),
554            DataType::DateTime => Value::DateTime(DateTimeValue {
555                year: 1970,
556                month: 1,
557                day: 1,
558                hour: 0,
559                minute: 0,
560                sec: 0,
561                microsec: 0,
562            }),
563            DataType::Time => Value::Time(TimeValue {
564                hour: 0,
565                minute: 0,
566                sec: 0,
567                microsec: 0,
568            }),
569            _ => Value::Null(NullType::Null),
570        }
571    }
572}
573
574impl<S: StorageClient> Clone for SchemaApi<S> {
575    fn clone(&self) -> Self {
576        Self {
577            storage: Arc::clone(&self.storage),
578        }
579    }
580}
581
582// Type conversion implementation
583impl From<PropertyDef> for linkrs_core::types::PropertyDef {
584    fn from(prop: PropertyDef) -> Self {
585        Self {
586            name: prop.name,
587            data_type: prop.data_type,
588            nullable: prop.nullable,
589            default: prop.default_value,
590            comment: prop.comment,
591            serial: false,
592        }
593    }
594}
595
596#[cfg(test)]
597mod tests {
598    use super::*;
599    use linkrs_storage::MockStorage;
600
601    fn create_mock_storage() -> Arc<RwLock<MockStorage>> {
602        Arc::new(RwLock::new(
603            MockStorage::new().expect("Failed to create MockStorage"),
604        ))
605    }
606
607    #[test]
608    fn test_schema_api_new() {
609        let storage = create_mock_storage();
610        let _schema_api = SchemaApi::new(storage);
611        // Creation was successful, and the test passed. Reaching this point indicates that the goal has been achieved.
612    }
613
614    #[test]
615    fn test_schema_api_clone() {
616        let storage = create_mock_storage();
617        let schema_api = SchemaApi::new(storage);
618        let _cloned = schema_api.clone();
619        // The cloning was successful, and the tests have passed. Reaching this point indicates that the entire process has been a success.
620    }
621
622    #[test]
623    fn test_property_def_conversion() {
624        let api_prop = PropertyDef {
625            name: "test".to_string(),
626            data_type: linkrs_core::DataType::String,
627            nullable: true,
628            default_value: None,
629            comment: Some("test comment".to_string()),
630        };
631
632        let core_prop: linkrs_core::types::PropertyDef = api_prop.into();
633        assert_eq!(core_prop.name, "test");
634        assert_eq!(core_prop.data_type, linkrs_core::DataType::String);
635        assert!(core_prop.nullable);
636        assert_eq!(core_prop.comment, Some("test comment".to_string()));
637    }
638}