1use indexmap::IndexMap;
22use serde::{Deserialize, Serialize};
23
24use crate::error::{Error, Result};
25use crate::models::cardinality::Cardinality;
26use crate::models::types::DataType;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
30pub struct EntityId(pub u32);
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
34pub struct AttributeId(pub u32);
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
38pub struct RelationshipId(pub u32);
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
42pub struct SpecializationId(pub u32);
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
46pub struct UnionId(pub u32);
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
50pub struct AssociativeEntityId(pub u32);
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
59pub enum AttributeKind {
60 #[default]
62 Simple,
63 Composite,
65 Multivalued {
68 min: u32,
70 max: Option<u32>,
72 },
73 Derived,
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct Entity {
80 pub id: EntityId,
82 pub name: String,
84 pub note: String,
86 pub attributes: Vec<AttributeId>,
88 pub weak: bool,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct Attribute {
96 pub id: AttributeId,
98 pub name: String,
100 pub data_type: DataType,
102 pub is_primary: bool,
104 pub is_partial_key: bool,
108 pub is_optional: bool,
110 pub kind: AttributeKind,
112 pub children: Vec<AttributeId>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct RelationshipEndpoint {
129 pub entity: EntityId,
131 pub cardinality: Cardinality,
134 pub role: Option<String>,
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140pub struct Relationship {
141 pub id: RelationshipId,
143 pub name: String,
145 pub endpoints: Vec<RelationshipEndpoint>,
148 pub attributes: Vec<AttributeId>,
150}
151
152impl Relationship {
153 pub fn is_self(&self) -> bool {
155 if self.endpoints.is_empty() {
156 return false;
157 }
158 let first = self.endpoints[0].entity;
159 self.endpoints.iter().all(|e| e.entity == first)
160 }
161
162 pub fn is_binary(&self) -> bool {
164 self.endpoints.len() == 2
165 }
166
167 pub fn is_nary(&self) -> bool {
169 self.endpoints.len() >= 3
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
178pub struct SpecializationKind {
179 pub total: bool,
181 pub overlapping: bool,
183}
184
185impl SpecializationKind {
186 pub const PARTIAL_DISJOINT: Self = Self { total: false, overlapping: false };
188 pub const TOTAL_DISJOINT: Self = Self { total: true, overlapping: false };
190 pub const PARTIAL_OVERLAPPING: Self = Self { total: false, overlapping: true };
192 pub const TOTAL_OVERLAPPING: Self = Self { total: true, overlapping: true };
194}
195
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199pub struct Specialization {
200 pub id: SpecializationId,
202 pub name: String,
204 pub parent: EntityId,
206 pub children: Vec<EntityId>,
208 pub kind: SpecializationKind,
210}
211
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215pub struct Union {
216 pub id: UnionId,
218 pub name: String,
220 pub parents: Vec<EntityId>,
222 pub category: EntityId,
224}
225
226#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct AssociativeEntity {
230 pub id: AssociativeEntityId,
232 pub relationship: RelationshipId,
234 pub name: String,
236}
237
238#[derive(Debug, Clone, Default, Serialize, Deserialize)]
244pub struct ConceptualModel {
245 pub name: String,
247 next_id: u32,
249 pub entities: IndexMap<EntityId, Entity>,
251 pub attributes: IndexMap<AttributeId, Attribute>,
253 pub relationships: IndexMap<RelationshipId, Relationship>,
255 pub specializations: IndexMap<SpecializationId, Specialization>,
257 pub unions: IndexMap<UnionId, Union>,
259 pub associative_entities: IndexMap<AssociativeEntityId, AssociativeEntity>,
261}
262
263impl ConceptualModel {
264 pub fn new(name: impl Into<String>) -> Self {
266 Self {
267 name: name.into(),
268 ..Self::default()
269 }
270 }
271
272 fn mint(&mut self) -> u32 {
273 self.next_id = self.next_id.checked_add(1).expect("ID space exhausted");
274 self.next_id
275 }
276
277 pub fn add_entity(&mut self, name: impl Into<String>) -> EntityId {
279 let id = EntityId(self.mint());
280 self.entities.insert(
281 id,
282 Entity {
283 id,
284 name: name.into(),
285 note: String::new(),
286 attributes: Vec::new(),
287 weak: false,
288 },
289 );
290 id
291 }
292
293 pub fn add_weak_entity(&mut self, name: impl Into<String>) -> EntityId {
297 let id = self.add_entity(name);
298 self.entity_mut(id).expect("just inserted").weak = true;
299 id
300 }
301
302 pub fn entity(&self, id: EntityId) -> Result<&Entity> {
304 self.entities
305 .get(&id)
306 .ok_or_else(|| Error::UnknownReference { kind: "entity", id: format!("{}", id.0) })
307 }
308
309 pub fn entity_mut(&mut self, id: EntityId) -> Result<&mut Entity> {
311 self.entities
312 .get_mut(&id)
313 .ok_or_else(|| Error::UnknownReference { kind: "entity", id: format!("{}", id.0) })
314 }
315
316 pub fn add_attribute(
321 &mut self,
322 owner: AttributeOwner,
323 name: impl Into<String>,
324 data_type: DataType,
325 ) -> Result<AttributeId> {
326 let id = AttributeId(self.mint());
327 self.attributes.insert(
328 id,
329 Attribute {
330 id,
331 name: name.into(),
332 data_type,
333 is_primary: false,
334 is_partial_key: false,
335 is_optional: false,
336 kind: AttributeKind::Simple,
337 children: Vec::new(),
338 },
339 );
340 match owner {
341 AttributeOwner::Entity(eid) => self.entity_mut(eid)?.attributes.push(id),
342 AttributeOwner::Relationship(rid) => self.relationship_mut(rid)?.attributes.push(id),
343 }
344 Ok(id)
345 }
346
347 pub fn add_primary_attribute(
349 &mut self,
350 entity: EntityId,
351 name: impl Into<String>,
352 data_type: DataType,
353 ) -> Result<AttributeId> {
354 let id = self.add_attribute(AttributeOwner::Entity(entity), name, data_type)?;
355 let attr = self.attribute_mut(id)?;
356 attr.is_primary = true;
357 Ok(id)
358 }
359
360 pub fn attribute(&self, id: AttributeId) -> Result<&Attribute> {
362 self.attributes
363 .get(&id)
364 .ok_or_else(|| Error::UnknownReference { kind: "attribute", id: format!("{}", id.0) })
365 }
366
367 pub fn attribute_mut(&mut self, id: AttributeId) -> Result<&mut Attribute> {
369 self.attributes
370 .get_mut(&id)
371 .ok_or_else(|| Error::UnknownReference { kind: "attribute", id: format!("{}", id.0) })
372 }
373
374 pub fn relate(
379 &mut self,
380 name: impl Into<String>,
381 first: EntityId,
382 first_card: Cardinality,
383 ) -> RelationshipBuilder<'_> {
384 let id = RelationshipId(self.mint());
385 let rel = Relationship {
386 id,
387 name: name.into(),
388 endpoints: vec![RelationshipEndpoint {
389 entity: first,
390 cardinality: first_card,
391 role: None,
392 }],
393 attributes: Vec::new(),
394 };
395 self.relationships.insert(id, rel);
396 RelationshipBuilder { model: self, id }
397 }
398
399 pub fn relationship(&self, id: RelationshipId) -> Result<&Relationship> {
401 self.relationships.get(&id).ok_or_else(|| Error::UnknownReference {
402 kind: "relationship",
403 id: format!("{}", id.0),
404 })
405 }
406
407 pub fn relationship_mut(&mut self, id: RelationshipId) -> Result<&mut Relationship> {
409 self.relationships.get_mut(&id).ok_or_else(|| Error::UnknownReference {
410 kind: "relationship",
411 id: format!("{}", id.0),
412 })
413 }
414
415 pub fn add_specialization(
417 &mut self,
418 name: impl Into<String>,
419 parent: EntityId,
420 children: Vec<EntityId>,
421 kind: SpecializationKind,
422 ) -> Result<SpecializationId> {
423 if children.len() < 2 {
424 return Err(Error::InvalidSpecialization(format!(
425 "specialization `{}` must have at least 2 children, got {}",
426 name.into(),
427 children.len()
428 )));
429 }
430 let _ = self.entity(parent)?;
431 for c in &children {
432 let _ = self.entity(*c)?;
433 }
434 let id = SpecializationId(self.mint());
435 let name = name.into();
436 self.specializations.insert(id, Specialization { id, name, parent, children, kind });
437 Ok(id)
438 }
439
440 pub fn add_union(
442 &mut self,
443 name: impl Into<String>,
444 parents: Vec<EntityId>,
445 category: EntityId,
446 ) -> Result<UnionId> {
447 if parents.len() < 2 {
448 return Err(Error::InvalidSpecialization(format!(
449 "union `{}` must have at least 2 parents, got {}",
450 name.into(),
451 parents.len()
452 )));
453 }
454 let _ = self.entity(category)?;
455 for p in &parents {
456 let _ = self.entity(*p)?;
457 }
458 let id = UnionId(self.mint());
459 self.unions
460 .insert(id, Union { id, name: name.into(), parents, category });
461 Ok(id)
462 }
463
464 pub fn add_associative_entity(
466 &mut self,
467 relationship: RelationshipId,
468 ) -> Result<AssociativeEntityId> {
469 let rel = self.relationship(relationship)?;
470 let name = rel.name.clone();
471 let id = AssociativeEntityId(self.mint());
472 self.associative_entities
473 .insert(id, AssociativeEntity { id, relationship, name });
474 Ok(id)
475 }
476}
477
478#[derive(Debug, Clone, Copy)]
480pub enum AttributeOwner {
481 Entity(EntityId),
483 Relationship(RelationshipId),
485}
486
487pub struct RelationshipBuilder<'m> {
492 model: &'m mut ConceptualModel,
493 id: RelationshipId,
494}
495
496impl<'m> RelationshipBuilder<'m> {
497 pub fn with(self, entity: EntityId, cardinality: Cardinality) -> Self {
499 if let Some(rel) = self.model.relationships.get_mut(&self.id) {
500 rel.endpoints.push(RelationshipEndpoint { entity, cardinality, role: None });
501 }
502 self
503 }
504
505 pub fn with_role(self, role: impl Into<String>) -> Self {
507 if let Some(rel) = self.model.relationships.get_mut(&self.id) {
508 if let Some(last) = rel.endpoints.last_mut() {
509 last.role = Some(role.into());
510 }
511 }
512 self
513 }
514
515 pub fn carry(self, name: impl Into<String>, data_type: DataType) -> Self {
517 let _ = self
518 .model
519 .add_attribute(AttributeOwner::Relationship(self.id), name, data_type);
520 self
521 }
522
523 pub fn id(self) -> RelationshipId {
525 self.id
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 fn sample() -> ConceptualModel {
534 let mut m = ConceptualModel::new("library");
535 let book = m.add_entity("Book");
536 let author = m.add_entity("Author");
537 m.add_primary_attribute(book, "id", DataType::Integer).unwrap();
538 m.add_attribute(AttributeOwner::Entity(book), "title", DataType::Varchar(255)).unwrap();
539 m.add_primary_attribute(author, "id", DataType::Integer).unwrap();
540 m.relate("wrote", book, Cardinality::ZeroToMany)
541 .with(author, Cardinality::OneToMany)
542 .id();
543 m
544 }
545
546 #[test]
547 fn build_basic_model() {
548 let m = sample();
549 assert_eq!(m.entities.len(), 2);
550 assert_eq!(m.attributes.len(), 3);
551 assert_eq!(m.relationships.len(), 1);
552 let rel = m.relationships.values().next().unwrap();
553 assert!(rel.is_binary());
554 assert!(!rel.is_self());
555 }
556
557 #[test]
558 fn relationship_self_check() {
559 let mut m = ConceptualModel::new("hr");
560 let person = m.add_entity("Person");
561 let rel = m
562 .relate("manages", person, Cardinality::ZeroToOne)
563 .with(person, Cardinality::ZeroToMany)
564 .id();
565 assert!(m.relationship(rel).unwrap().is_self());
566 }
567
568 #[test]
569 fn specialization_requires_two_children() {
570 let mut m = ConceptualModel::new("vehicles");
571 let v = m.add_entity("Vehicle");
572 let car = m.add_entity("Car");
573 let err = m
574 .add_specialization("kind", v, vec![car], SpecializationKind::PARTIAL_DISJOINT)
575 .unwrap_err();
576 assert!(matches!(err, Error::InvalidSpecialization(_)));
577 }
578
579 #[test]
580 fn unknown_entity_error() {
581 let m = ConceptualModel::new("x");
582 let err = m.entity(EntityId(99)).unwrap_err();
583 assert!(matches!(err, Error::UnknownReference { kind: "entity", .. }));
584 }
585
586 #[test]
587 fn json_round_trip() {
588 let m = sample();
589 let s = serde_json::to_string(&m).unwrap();
590 let back: ConceptualModel = serde_json::from_str(&s).unwrap();
591 assert_eq!(m.entities.len(), back.entities.len());
592 assert_eq!(m.attributes.len(), back.attributes.len());
593 assert_eq!(m.relationships.len(), back.relationships.len());
594 }
595}