1use std::sync::Arc;
4
5use selene_core::{
6 ByteStringType, Change, CharacterStringType, DbString, EdgeEndpointDef as CoreEdgeEndpointDef,
7 GraphTypeId, LabelSet, PredefinedValueType, PropertyDef, PropertyValueType, SchemaChange,
8 ValueType,
9};
10use smallvec::SmallVec;
11
12use crate::{
13 DropBehavior, EdgeEndpointDef, EdgeTypeDef, GraphError, GraphResult, GraphTypeDef, Mutator,
14 NodeTypeDef, PropertyElementType, PropertyTypeDef, RecordFieldType, RecordFieldTypes,
15 ValidationMode,
16 graph_types::{MAX_LIST_TYPE_NESTING, MAX_RECORD_TYPE_NESTING},
17};
18
19const OPEN_GRAPH_CATALOG_DDL: &str =
20 "open graph (GG01) does not support catalog type DDL -- use a closed graph (GG02)";
21
22impl<'tx, 'g> Mutator<'tx, 'g> {
23 pub fn create_node_type(
30 &mut self,
31 name: DbString,
32 key_labels: LabelSet,
33 properties: Vec<PropertyTypeDef>,
34 validation_mode: ValidationMode,
35 ) -> GraphResult<()> {
36 let mut graph_type = self.current_graph_type()?;
37 if graph_type
38 .node_types
39 .iter()
40 .any(|node_type| node_type.name == name)
41 {
42 return Err(GraphError::Inconsistent {
43 reason: format!("node type {name} already exists"),
44 });
45 }
46 let node_type = NodeTypeDef {
47 name: name.clone(),
48 key_labels,
49 properties,
50 validation_mode,
51 };
52 graph_type.node_types.push(node_type.clone());
53 graph_type.validate_ref()?;
54 let graph_id = self.txn.read().graph_id();
55 self.txn.guard_mut().meta.bound_type = Some(Arc::new(graph_type));
56 self.txn.changes.push(Change::SchemaChanged {
57 graph: graph_id,
58 change: SchemaChange::NodeTypeAddedV2 {
59 graph_type: implicit_graph_type_id(),
60 label: name,
61 def: core_node_type_def(&node_type)?,
62 },
63 });
64 Ok(())
65 }
66
67 pub fn create_edge_type(
75 &mut self,
76 name: DbString,
77 label: DbString,
78 source_node_type: EdgeEndpointDef,
79 target_node_type: EdgeEndpointDef,
80 properties: Vec<PropertyTypeDef>,
81 validation_mode: ValidationMode,
82 ) -> GraphResult<()> {
83 let mut graph_type = self.current_graph_type()?;
84 if graph_type
85 .edge_types
86 .iter()
87 .any(|edge_type| edge_type.name == name)
88 {
89 return Err(GraphError::Inconsistent {
90 reason: format!("edge type {name} already exists"),
91 });
92 }
93 let edge_type = EdgeTypeDef {
94 name,
95 label: label.clone(),
96 source_node_type,
97 target_node_type,
98 properties,
99 validation_mode,
100 };
101 graph_type.edge_types.push(edge_type.clone());
102 graph_type.validate_ref()?;
103 let graph_id = self.txn.read().graph_id();
104 self.txn.guard_mut().meta.bound_type = Some(Arc::new(graph_type.clone()));
105 self.txn.changes.push(Change::SchemaChanged {
106 graph: graph_id,
107 change: SchemaChange::EdgeTypeAddedV2 {
108 graph_type: implicit_graph_type_id(),
109 label,
110 def: core_edge_type_def(&graph_type, &edge_type)?,
111 },
112 });
113 Ok(())
114 }
115
116 pub fn drop_node_type(&mut self, name: DbString, behavior: DropBehavior) -> GraphResult<()> {
139 let graph_type = self.current_graph_type()?;
140 let removed_index = graph_type
141 .node_type_index_for(name.clone())
142 .ok_or_else(|| GraphError::Inconsistent {
143 reason: format!("node type {name} does not exist"),
144 })?;
145 match behavior {
146 DropBehavior::Restrict => {
147 let live = self
151 .txn
152 .read()
153 .nodes_with_label(&name)
154 .map_or(0, roaring::RoaringBitmap::len);
155 if live > 0 {
156 return Err(GraphError::Inconsistent {
157 reason: format!(
158 "cannot drop node type {name}: {live} instance(s) still exist; use CASCADE to remove them"
159 ),
160 });
161 }
162 for edge_type in &graph_type.edge_types {
166 if endpoint_references_node(&edge_type.source_node_type, removed_index)
167 || endpoint_references_node(&edge_type.target_node_type, removed_index)
168 {
169 return Err(GraphError::Inconsistent {
170 reason: format!(
171 "cannot drop node type {name}: edge type {} still references it",
172 edge_type.name
173 ),
174 });
175 }
176 }
177 }
178 DropBehavior::Cascade => {
179 self.truncate_node_type(name.clone())?;
182 }
183 }
184 for edge_type in &graph_type.edge_types {
191 if endpoint_depends_on_shifted_node(&edge_type.source_node_type, removed_index)
192 || endpoint_depends_on_shifted_node(&edge_type.target_node_type, removed_index)
193 {
194 return Err(GraphError::Inconsistent {
195 reason: format!(
196 "cannot drop node type {name}: edge type {} still depends on node-type indexes that would require reindexing",
197 edge_type.name
198 ),
199 });
200 }
201 }
202 let next = graph_type
203 .without_node_type(name.clone())
204 .expect("node type existed above");
205 next.validate_ref()?;
206 let graph_id = self.txn.read().graph_id();
207 self.txn.guard_mut().meta.bound_type = Some(Arc::new(next));
208 self.txn.changes.push(Change::SchemaChanged {
209 graph: graph_id,
210 change: SchemaChange::NodeTypeDropped {
211 graph_type: implicit_graph_type_id(),
212 name,
213 },
214 });
215 Ok(())
216 }
217
218 pub fn drop_edge_type(&mut self, name: DbString, behavior: DropBehavior) -> GraphResult<()> {
237 let graph_type = self.current_graph_type()?;
238 if graph_type.edge_type_index_for(name.clone()).is_none() {
239 return Err(GraphError::Inconsistent {
240 reason: format!("edge type {name} does not exist"),
241 });
242 }
243 match behavior {
244 DropBehavior::Restrict => {
245 let live = self
246 .txn
247 .read()
248 .edges_with_label(&name)
249 .map_or(0, roaring::RoaringBitmap::len);
250 if live > 0 {
251 return Err(GraphError::Inconsistent {
252 reason: format!(
253 "cannot drop edge type {name}: {live} instance(s) still exist; use CASCADE to remove them"
254 ),
255 });
256 }
257 }
258 DropBehavior::Cascade => {
259 self.truncate_edge_type(name.clone())?;
260 }
261 }
262 let next = graph_type
263 .without_edge_type(name.clone())
264 .expect("edge type existed above");
265 next.validate_ref()?;
266 let graph_id = self.txn.read().graph_id();
267 self.txn.guard_mut().meta.bound_type = Some(Arc::new(next));
268 self.txn.changes.push(Change::SchemaChanged {
269 graph: graph_id,
270 change: SchemaChange::EdgeTypeDropped {
271 graph_type: implicit_graph_type_id(),
272 name,
273 },
274 });
275 Ok(())
276 }
277
278 fn current_graph_type(&self) -> GraphResult<GraphTypeDef> {
279 self.txn
280 .read()
281 .meta
282 .bound_type
283 .as_deref()
284 .cloned()
285 .ok_or_else(|| GraphError::Inconsistent {
286 reason: OPEN_GRAPH_CATALOG_DDL.to_owned(),
287 })
288 }
289}
290
291fn implicit_graph_type_id() -> GraphTypeId {
296 GraphTypeId::new(1).expect("implicit graph type id")
297}
298
299fn endpoint_references_node(endpoint: &EdgeEndpointDef, removed_index: u32) -> bool {
306 match endpoint {
307 EdgeEndpointDef::Any => false,
308 EdgeEndpointDef::NodeType(index) => *index == removed_index,
309 EdgeEndpointDef::OneOf(indices) => indices.contains(&removed_index),
310 }
311}
312
313fn endpoint_depends_on_shifted_node(endpoint: &EdgeEndpointDef, removed_index: u32) -> bool {
314 match endpoint {
318 EdgeEndpointDef::Any => false,
319 EdgeEndpointDef::NodeType(index) => *index >= removed_index,
320 EdgeEndpointDef::OneOf(indices) => indices.iter().any(|index| *index >= removed_index),
321 }
322}
323
324fn core_node_type_def(node_type: &NodeTypeDef) -> GraphResult<selene_core::NodeTypeDef> {
325 Ok(selene_core::NodeTypeDef {
326 labels: node_type.key_labels.clone(),
327 properties: core_node_properties(&node_type.properties)?,
328 key: None,
329 validation_mode: core_validation_mode(node_type.validation_mode),
330 })
331}
332
333fn core_edge_type_def(
334 graph_type: &GraphTypeDef,
335 edge_type: &EdgeTypeDef,
336) -> GraphResult<selene_core::EdgeTypeDef> {
337 Ok(selene_core::EdgeTypeDef {
338 label: edge_type.label.clone(),
339 source_node_type: core_edge_endpoint_def(
340 graph_type,
341 edge_type.name.clone(),
342 &edge_type.source_node_type,
343 )?,
344 target_node_type: core_edge_endpoint_def(
345 graph_type,
346 edge_type.name.clone(),
347 &edge_type.target_node_type,
348 )?,
349 properties: core_edge_properties(&edge_type.properties)?,
350 validation_mode: core_validation_mode(edge_type.validation_mode),
351 })
352}
353
354fn core_edge_endpoint_def(
355 graph_type: &GraphTypeDef,
356 edge_name: DbString,
357 endpoint: &EdgeEndpointDef,
358) -> GraphResult<CoreEdgeEndpointDef> {
359 match endpoint {
360 EdgeEndpointDef::Any => Ok(CoreEdgeEndpointDef::Any),
361 EdgeEndpointDef::NodeType(index) => graph_type
362 .node_types
363 .get(*index as usize)
364 .map(|node_type| {
365 CoreEdgeEndpointDef::NodeType(selene_core::NodeTypeRef(node_type.name.clone()))
366 })
367 .ok_or_else(|| GraphError::Inconsistent {
368 reason: format!("edge type {edge_name} references invalid node type {index}"),
369 }),
370 EdgeEndpointDef::OneOf(indices) => {
371 let mut refs: SmallVec<[selene_core::NodeTypeRef; 4]> = SmallVec::new();
372 for index in indices {
373 let node_type = graph_type.node_types.get(*index as usize).ok_or_else(|| {
374 GraphError::Inconsistent {
375 reason: format!(
376 "edge type {edge_name} OneOf endpoint references invalid node type {index}"
377 ),
378 }
379 })?;
380 refs.push(selene_core::NodeTypeRef(node_type.name.clone()));
381 }
382 Ok(CoreEdgeEndpointDef::OneOf(refs))
383 }
384 }
385}
386
387fn core_node_properties(properties: &[PropertyTypeDef]) -> GraphResult<SmallVec<[PropertyDef; 8]>> {
388 let mut out = SmallVec::new();
389 for property in properties {
390 out.push(PropertyDef {
391 name: property.name.clone(),
392 value_type: core_value_type(
393 property.value_type,
394 property.list_element_type.as_ref(),
395 property.decimal_type,
396 property.character_string_type,
397 property.byte_string_type,
398 property.required,
399 )?,
400 nullable: !property.required,
401 default: property
402 .default
403 .as_ref()
404 .map(|default| default.to_value())
405 .transpose()?,
406 immutable: property.immutable,
407 unique: property.unique,
408 record_fields: core_record_fields(
409 property.value_type,
410 property.record_field_types.as_ref(),
411 )?,
412 });
413 }
414 Ok(out)
415}
416
417fn core_edge_properties(properties: &[PropertyTypeDef]) -> GraphResult<SmallVec<[PropertyDef; 4]>> {
418 let mut out = SmallVec::new();
419 for property in properties {
420 out.push(PropertyDef {
421 name: property.name.clone(),
422 value_type: core_value_type(
423 property.value_type,
424 property.list_element_type.as_ref(),
425 property.decimal_type,
426 property.character_string_type,
427 property.byte_string_type,
428 property.required,
429 )?,
430 nullable: !property.required,
431 default: property
432 .default
433 .as_ref()
434 .map(|default| default.to_value())
435 .transpose()?,
436 immutable: property.immutable,
437 unique: property.unique,
438 record_fields: core_record_fields(
439 property.value_type,
440 property.record_field_types.as_ref(),
441 )?,
442 });
443 }
444 Ok(out)
445}
446
447const fn core_validation_mode(mode: ValidationMode) -> selene_core::ValidationMode {
448 match mode {
449 ValidationMode::Strict => selene_core::ValidationMode::Strict,
450 ValidationMode::Warn => selene_core::ValidationMode::Warn,
451 }
452}
453
454fn core_value_type(
455 value_type: PropertyValueType,
456 list_element_type: Option<&PropertyElementType>,
457 decimal_type: Option<selene_core::DecimalType>,
458 character_string_type: Option<CharacterStringType>,
459 byte_string_type: Option<ByteStringType>,
460 required: bool,
461) -> GraphResult<ValueType> {
462 let mut value_type = if value_type == PropertyValueType::List {
463 let element_type = list_element_type.ok_or_else(|| GraphError::Inconsistent {
464 reason: "LIST property definition is missing element type".to_owned(),
465 })?;
466 ValueType::list_of(core_element_value_type(element_type, 1)?)
467 } else {
468 core_scalar_value_type(
469 value_type,
470 decimal_type,
471 character_string_type,
472 byte_string_type,
473 )
474 };
475 value_type.not_null = required;
476 Ok(value_type)
477}
478
479fn core_element_value_type(
480 element_type: &PropertyElementType,
481 depth: u32,
482) -> GraphResult<ValueType> {
483 if depth > MAX_LIST_TYPE_NESTING {
484 return Err(GraphError::Inconsistent {
485 reason: "LIST property definition exceeds nesting limit".to_owned(),
486 });
487 }
488 match element_type {
489 PropertyElementType::Scalar(value_type) => {
490 Ok(core_scalar_value_type(*value_type, None, None, None))
491 }
492 PropertyElementType::CharacterString(character_string_type) => Ok(core_scalar_value_type(
493 PropertyValueType::String,
494 None,
495 Some(*character_string_type),
496 None,
497 )),
498 PropertyElementType::Decimal(decimal_type) => Ok(core_scalar_value_type(
499 PropertyValueType::Decimal,
500 Some(*decimal_type),
501 None,
502 None,
503 )),
504 PropertyElementType::ByteString(byte_string_type) => Ok(core_scalar_value_type(
505 PropertyValueType::Bytes,
506 None,
507 None,
508 Some(*byte_string_type),
509 )),
510 PropertyElementType::List(inner) => Ok(ValueType::list_of(core_element_value_type(
511 inner,
512 depth + 1,
513 )?)),
514 PropertyElementType::NotNull(inner) => {
515 let mut value_type = core_element_value_type(inner, depth)?;
516 value_type.not_null = true;
517 Ok(value_type)
518 }
519 }
520}
521
522fn core_scalar_value_type(
523 value_type: PropertyValueType,
524 decimal_type: Option<selene_core::DecimalType>,
525 character_string_type: Option<CharacterStringType>,
526 byte_string_type: Option<ByteStringType>,
527) -> ValueType {
528 let predefined = match value_type {
529 PropertyValueType::Bool => Some(PredefinedValueType::Bool),
530 PropertyValueType::Int => Some(PredefinedValueType::Int),
531 PropertyValueType::Uint => Some(PredefinedValueType::Uint),
532 PropertyValueType::Int128 => Some(PredefinedValueType::Int128),
533 PropertyValueType::Uint128 => Some(PredefinedValueType::Uint128),
534 PropertyValueType::Float => Some(PredefinedValueType::Float),
535 PropertyValueType::Float32 => Some(PredefinedValueType::Float32),
536 PropertyValueType::Decimal => Some(PredefinedValueType::Decimal),
537 PropertyValueType::String => Some(PredefinedValueType::String),
538 PropertyValueType::Bytes => Some(PredefinedValueType::Bytes),
539 PropertyValueType::Path => Some(PredefinedValueType::Path),
540 PropertyValueType::NodeRef => Some(PredefinedValueType::NodeRef),
541 PropertyValueType::EdgeRef => Some(PredefinedValueType::EdgeRef),
542 PropertyValueType::GraphRef => Some(PredefinedValueType::GraphRef),
543 PropertyValueType::TableRef => Some(PredefinedValueType::TableRef),
544 PropertyValueType::ZonedDateTime => Some(PredefinedValueType::ZonedDateTime),
545 PropertyValueType::LocalDateTime => Some(PredefinedValueType::LocalDateTime),
546 PropertyValueType::Date => Some(PredefinedValueType::Date),
547 PropertyValueType::ZonedTime => Some(PredefinedValueType::ZonedTime),
548 PropertyValueType::LocalTime => Some(PredefinedValueType::LocalTime),
549 PropertyValueType::Duration => Some(PredefinedValueType::Duration),
550 PropertyValueType::DurationYearToMonth => Some(PredefinedValueType::DurationYearToMonth),
551 PropertyValueType::DurationDayToSecond => Some(PredefinedValueType::DurationDayToSecond),
552 PropertyValueType::Uuid => Some(PredefinedValueType::Uuid),
553 PropertyValueType::Vector => Some(PredefinedValueType::Vector),
554 PropertyValueType::Json => Some(PredefinedValueType::Json),
555 PropertyValueType::List
556 | PropertyValueType::Record
557 | PropertyValueType::RecordTyped
558 | PropertyValueType::Null => None,
559 };
560 ValueType {
561 predefined,
562 decimal_type: if value_type == PropertyValueType::Decimal {
563 decimal_type
564 } else {
565 None
566 },
567 character_string_type: if value_type == PropertyValueType::String {
568 character_string_type
569 } else {
570 None
571 },
572 byte_string_type: if value_type == PropertyValueType::Bytes {
573 byte_string_type
574 } else {
575 None
576 },
577 union: None,
578 list_of: None,
579 record: None,
580 not_null: false,
581 cardinality: selene_core::ValueTypeCardinality::ExactlyOne,
582 }
583}
584
585fn core_record_fields(
594 value_type: PropertyValueType,
595 fields: Option<&RecordFieldTypes>,
596) -> GraphResult<Option<Box<selene_core::RecordFieldStructure>>> {
597 match (value_type, fields) {
598 (PropertyValueType::RecordTyped, Some(fields)) => {
599 Ok(Some(Box::new(core_record_field_structure(fields, 1)?)))
600 }
601 (PropertyValueType::RecordTyped, None) => {
602 Ok(Some(Box::new(selene_core::RecordFieldStructure::Open)))
603 }
604 _ => Ok(None),
605 }
606}
607
608fn core_record_field_structure(
609 fields: &RecordFieldTypes,
610 depth: u32,
611) -> GraphResult<selene_core::RecordFieldStructure> {
612 if depth > MAX_RECORD_TYPE_NESTING {
613 return Err(GraphError::Inconsistent {
614 reason: "RECORD property definition exceeds nesting limit".to_owned(),
615 });
616 }
617 let defs = fields
618 .0
619 .iter()
620 .map(|field| {
621 Ok(selene_core::RecordFieldStructureDef {
622 name: field.name.clone(),
623 field_type: core_record_field_structure_type(&field.field_type, depth)?,
624 required: field.required,
625 })
626 })
627 .collect::<GraphResult<Vec<_>>>()?;
628 Ok(selene_core::RecordFieldStructure::Closed(defs))
629}
630
631fn core_record_field_structure_type(
632 field_type: &RecordFieldType,
633 depth: u32,
634) -> GraphResult<selene_core::RecordFieldStructureType> {
635 Ok(match field_type {
636 RecordFieldType::Scalar(value_type) => {
637 selene_core::RecordFieldStructureType::Scalar(*value_type)
638 }
639 RecordFieldType::CharacterString(character_string_type) => {
640 selene_core::RecordFieldStructureType::CharacterString(*character_string_type)
641 }
642 RecordFieldType::Decimal(decimal_type) => {
643 selene_core::RecordFieldStructureType::Decimal(*decimal_type)
644 }
645 RecordFieldType::ByteString(byte_string_type) => {
646 selene_core::RecordFieldStructureType::ByteString(*byte_string_type)
647 }
648 RecordFieldType::List(inner) => selene_core::RecordFieldStructureType::List(Box::new(
649 core_record_field_structure_type(inner, depth + 1)?,
650 )),
651 RecordFieldType::OpenRecord => selene_core::RecordFieldStructureType::Record(Box::new(
652 selene_core::RecordFieldStructure::Open,
653 )),
654 RecordFieldType::Record(inner) => selene_core::RecordFieldStructureType::Record(Box::new(
655 core_record_field_structure(inner, depth + 1)?,
656 )),
657 RecordFieldType::NotNull(inner) => selene_core::RecordFieldStructureType::NotNull(
658 Box::new(core_record_field_structure_type(inner, depth)?),
659 ),
660 })
661}
662
663#[cfg(test)]
664mod tests;