1use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
24use std::fmt;
25
26use crate::{AdapterError, SymbolTable};
27
28#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
33pub enum DomainComputation {
34 Filter {
39 base: String,
41 predicate: String,
43 },
44
45 Union {
49 domains: Vec<String>,
51 },
52
53 Intersection {
57 domains: Vec<String>,
59 },
60
61 Difference {
65 base: String,
67 subtract: String,
69 },
70
71 Product {
75 domains: Vec<String>,
77 },
78
79 PowerSet {
83 base: String,
85 },
86
87 Projection {
91 product: String,
93 index: usize,
95 },
96
97 Custom {
101 description: String,
103 formula: String,
105 },
106}
107
108impl fmt::Display for DomainComputation {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 Self::Filter { base, predicate } => write!(f, "{{ x ∈ {} | {}(x) }}", base, predicate),
112 Self::Union { domains } => write!(f, "{}", domains.join(" ∪ ")),
113 Self::Intersection { domains } => write!(f, "{}", domains.join(" ∩ ")),
114 Self::Difference { base, subtract } => write!(f, "{} \\ {}", base, subtract),
115 Self::Product { domains } => write!(f, "{}", domains.join(" × ")),
116 Self::PowerSet { base } => write!(f, "℘({})", base),
117 Self::Projection { product, index } => write!(f, "π{}({})", index, product),
118 Self::Custom { description, .. } => write!(f, "{}", description),
119 }
120 }
121}
122
123#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
151pub struct ComputedDomain {
152 name: String,
154 computation: DomainComputation,
156 cardinality_estimate: Option<usize>,
158 materialized: bool,
160}
161
162impl ComputedDomain {
163 pub fn new(name: impl Into<String>, computation: DomainComputation) -> Self {
179 Self {
180 name: name.into(),
181 computation,
182 cardinality_estimate: None,
183 materialized: false,
184 }
185 }
186
187 pub fn with_cardinality_estimate(mut self, estimate: usize) -> Self {
203 self.cardinality_estimate = Some(estimate);
204 self
205 }
206
207 pub fn name(&self) -> &str {
209 &self.name
210 }
211
212 pub fn computation(&self) -> &DomainComputation {
214 &self.computation
215 }
216
217 pub fn cardinality_estimate(&self) -> Option<usize> {
219 self.cardinality_estimate
220 }
221
222 pub fn is_materialized(&self) -> bool {
224 self.materialized
225 }
226
227 pub fn cardinality_bounds(&self, table: &SymbolTable) -> Result<(usize, usize), AdapterError> {
231 match &self.computation {
232 DomainComputation::Filter { base, .. } => {
233 let base_card = table
234 .get_domain(base)
235 .ok_or_else(|| AdapterError::UnknownDomain(base.clone()))?
236 .cardinality;
237 Ok((0, base_card))
239 }
240 DomainComputation::Union { domains } => {
241 let max_card = domains
242 .iter()
243 .map(|d| {
244 table
245 .get_domain(d)
246 .map(|info| info.cardinality)
247 .ok_or_else(|| AdapterError::UnknownDomain(d.clone()))
248 })
249 .collect::<Result<Vec<_>, _>>()?
250 .into_iter()
251 .max()
252 .unwrap_or(0);
253 let sum_card: usize = domains
254 .iter()
255 .map(|d| table.get_domain(d).unwrap().cardinality)
256 .sum();
257 Ok((max_card, sum_card))
258 }
259 DomainComputation::Intersection { domains } => {
260 let min_card = domains
261 .iter()
262 .map(|d| {
263 table
264 .get_domain(d)
265 .map(|info| info.cardinality)
266 .ok_or_else(|| AdapterError::UnknownDomain(d.clone()))
267 })
268 .collect::<Result<Vec<_>, _>>()?
269 .into_iter()
270 .min()
271 .unwrap_or(0);
272 Ok((0, min_card))
273 }
274 DomainComputation::Difference { base, subtract } => {
275 let base_card = table
276 .get_domain(base)
277 .ok_or_else(|| AdapterError::UnknownDomain(base.clone()))?
278 .cardinality;
279 let sub_card = table
280 .get_domain(subtract)
281 .ok_or_else(|| AdapterError::UnknownDomain(subtract.clone()))?
282 .cardinality;
283 Ok((0, base_card.saturating_sub(sub_card)))
284 }
285 DomainComputation::Product { domains } => {
286 let product: usize = domains
287 .iter()
288 .map(|d| {
289 table
290 .get_domain(d)
291 .map(|info| info.cardinality)
292 .ok_or_else(|| AdapterError::UnknownDomain(d.clone()))
293 })
294 .collect::<Result<Vec<_>, _>>()?
295 .into_iter()
296 .product();
297 Ok((product, product))
298 }
299 DomainComputation::PowerSet { base } => {
300 let base_card = table
301 .get_domain(base)
302 .ok_or_else(|| AdapterError::UnknownDomain(base.clone()))?
303 .cardinality;
304 let power = 2_usize.pow(base_card as u32);
305 Ok((power, power))
306 }
307 DomainComputation::Projection { product, index: _ } => {
308 let prod_domain = table
309 .get_domain(product)
310 .ok_or_else(|| AdapterError::UnknownDomain(product.clone()))?;
311 Ok((0, prod_domain.cardinality))
313 }
314 DomainComputation::Custom { .. } => {
315 if let Some(est) = self.cardinality_estimate {
317 Ok((0, est))
318 } else {
319 Err(AdapterError::InvalidOperation(
320 "Custom domain computation requires cardinality estimate".to_string(),
321 ))
322 }
323 }
324 }
325 }
326
327 pub fn validate(&self, table: &SymbolTable) -> Result<(), AdapterError> {
329 match &self.computation {
330 DomainComputation::Filter { base, predicate } => {
331 if table.get_domain(base).is_none() {
332 return Err(AdapterError::UnknownDomain(base.clone()));
333 }
334 if table.get_predicate(predicate).is_none() {
335 return Err(AdapterError::UnknownPredicate(predicate.clone()));
336 }
337 }
338 DomainComputation::Union { domains } | DomainComputation::Intersection { domains } => {
339 for domain in domains {
340 if table.get_domain(domain).is_none() {
341 return Err(AdapterError::UnknownDomain(domain.clone()));
342 }
343 }
344 }
345 DomainComputation::Difference { base, subtract } => {
346 if table.get_domain(base).is_none() {
347 return Err(AdapterError::UnknownDomain(base.clone()));
348 }
349 if table.get_domain(subtract).is_none() {
350 return Err(AdapterError::UnknownDomain(subtract.clone()));
351 }
352 }
353 DomainComputation::Product { domains } => {
354 for domain in domains {
355 if table.get_domain(domain).is_none() {
356 return Err(AdapterError::UnknownDomain(domain.clone()));
357 }
358 }
359 }
360 DomainComputation::PowerSet { base } => {
361 if table.get_domain(base).is_none() {
362 return Err(AdapterError::UnknownDomain(base.clone()));
363 }
364 }
365 DomainComputation::Projection { product, .. } => {
366 if table.get_domain(product).is_none() {
367 return Err(AdapterError::UnknownDomain(product.clone()));
368 }
369 }
370 DomainComputation::Custom { .. } => {
371 }
373 }
374 Ok(())
375 }
376
377 pub fn materialize(&mut self) {
379 self.materialized = true;
380 }
381}
382
383impl fmt::Display for ComputedDomain {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 write!(f, "{} := {}", self.name, self.computation)
386 }
387}
388
389#[derive(Clone, Debug, Default)]
394pub struct ComputedDomainRegistry {
395 domains: HashMap<String, ComputedDomain>,
397}
398
399impl ComputedDomainRegistry {
400 pub fn new() -> Self {
402 Self {
403 domains: HashMap::new(),
404 }
405 }
406
407 pub fn register(&mut self, domain: ComputedDomain) -> Result<(), AdapterError> {
425 let name = domain.name().to_string();
426 if self.domains.contains_key(&name) {
427 return Err(AdapterError::DuplicateDomain(name));
428 }
429 self.domains.insert(name, domain);
430 Ok(())
431 }
432
433 pub fn get(&self, name: &str) -> Option<&ComputedDomain> {
435 self.domains.get(name)
436 }
437
438 pub fn get_mut(&mut self, name: &str) -> Option<&mut ComputedDomain> {
440 self.domains.get_mut(name)
441 }
442
443 pub fn list(&self) -> Vec<&ComputedDomain> {
445 self.domains.values().collect()
446 }
447
448 pub fn remove(&mut self, name: &str) -> Option<ComputedDomain> {
450 self.domains.remove(name)
451 }
452
453 pub fn validate_all(&self, table: &SymbolTable) -> Result<(), Vec<AdapterError>> {
455 let errors: Vec<_> = self
456 .domains
457 .values()
458 .filter_map(|domain| domain.validate(table).err())
459 .collect();
460
461 if errors.is_empty() {
462 Ok(())
463 } else {
464 Err(errors)
465 }
466 }
467
468 pub fn len(&self) -> usize {
470 self.domains.len()
471 }
472
473 pub fn is_empty(&self) -> bool {
475 self.domains.is_empty()
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use crate::{DomainInfo, PredicateInfo};
483
484 #[test]
485 fn test_filter_computation() {
486 let comp = DomainComputation::Filter {
487 base: "Person".to_string(),
488 predicate: "is_adult".to_string(),
489 };
490 assert!(comp.to_string().contains("Person"));
491 assert!(comp.to_string().contains("is_adult"));
492 }
493
494 #[test]
495 fn test_union_computation() {
496 let comp = DomainComputation::Union {
497 domains: vec!["A".to_string(), "B".to_string(), "C".to_string()],
498 };
499 assert_eq!(comp.to_string(), "A ∪ B ∪ C");
500 }
501
502 #[test]
503 fn test_intersection_computation() {
504 let comp = DomainComputation::Intersection {
505 domains: vec!["A".to_string(), "B".to_string()],
506 };
507 assert_eq!(comp.to_string(), "A ∩ B");
508 }
509
510 #[test]
511 fn test_difference_computation() {
512 let comp = DomainComputation::Difference {
513 base: "A".to_string(),
514 subtract: "B".to_string(),
515 };
516 assert_eq!(comp.to_string(), "A \\ B");
517 }
518
519 #[test]
520 fn test_product_computation() {
521 let comp = DomainComputation::Product {
522 domains: vec!["A".to_string(), "B".to_string()],
523 };
524 assert_eq!(comp.to_string(), "A × B");
525 }
526
527 #[test]
528 fn test_powerset_computation() {
529 let comp = DomainComputation::PowerSet {
530 base: "A".to_string(),
531 };
532 assert_eq!(comp.to_string(), "℘(A)");
533 }
534
535 #[test]
536 fn test_computed_domain_creation() {
537 let domain = ComputedDomain::new(
538 "Adults",
539 DomainComputation::Filter {
540 base: "Person".to_string(),
541 predicate: "is_adult".to_string(),
542 },
543 );
544 assert_eq!(domain.name(), "Adults");
545 assert!(!domain.is_materialized());
546 }
547
548 #[test]
549 fn test_cardinality_estimate() {
550 let domain = ComputedDomain::new(
551 "Adults",
552 DomainComputation::Filter {
553 base: "Person".to_string(),
554 predicate: "is_adult".to_string(),
555 },
556 )
557 .with_cardinality_estimate(750);
558 assert_eq!(domain.cardinality_estimate(), Some(750));
559 }
560
561 #[test]
562 fn test_cardinality_bounds_filter() {
563 let mut table = SymbolTable::new();
564 table.add_domain(DomainInfo::new("Person", 1000)).unwrap();
565 table
566 .add_predicate(PredicateInfo::new("is_adult", vec!["Person".to_string()]))
567 .unwrap();
568
569 let domain = ComputedDomain::new(
570 "Adults",
571 DomainComputation::Filter {
572 base: "Person".to_string(),
573 predicate: "is_adult".to_string(),
574 },
575 );
576
577 let (lower, upper) = domain.cardinality_bounds(&table).unwrap();
578 assert_eq!(lower, 0);
579 assert_eq!(upper, 1000);
580 }
581
582 #[test]
583 fn test_cardinality_bounds_union() {
584 let mut table = SymbolTable::new();
585 table.add_domain(DomainInfo::new("A", 100)).unwrap();
586 table.add_domain(DomainInfo::new("B", 200)).unwrap();
587
588 let domain = ComputedDomain::new(
589 "AorB",
590 DomainComputation::Union {
591 domains: vec!["A".to_string(), "B".to_string()],
592 },
593 );
594
595 let (lower, upper) = domain.cardinality_bounds(&table).unwrap();
596 assert_eq!(lower, 200); assert_eq!(upper, 300); }
599
600 #[test]
601 fn test_cardinality_bounds_product() {
602 let mut table = SymbolTable::new();
603 table.add_domain(DomainInfo::new("A", 10)).unwrap();
604 table.add_domain(DomainInfo::new("B", 20)).unwrap();
605
606 let domain = ComputedDomain::new(
607 "AxB",
608 DomainComputation::Product {
609 domains: vec!["A".to_string(), "B".to_string()],
610 },
611 );
612
613 let (lower, upper) = domain.cardinality_bounds(&table).unwrap();
614 assert_eq!(lower, 200);
615 assert_eq!(upper, 200);
616 }
617
618 #[test]
619 fn test_validate_success() {
620 let mut table = SymbolTable::new();
621 table.add_domain(DomainInfo::new("Person", 1000)).unwrap();
622 table
623 .add_predicate(PredicateInfo::new("is_adult", vec!["Person".to_string()]))
624 .unwrap();
625
626 let domain = ComputedDomain::new(
627 "Adults",
628 DomainComputation::Filter {
629 base: "Person".to_string(),
630 predicate: "is_adult".to_string(),
631 },
632 );
633
634 assert!(domain.validate(&table).is_ok());
635 }
636
637 #[test]
638 fn test_validate_missing_domain() {
639 let table = SymbolTable::new();
640
641 let domain = ComputedDomain::new(
642 "Adults",
643 DomainComputation::Filter {
644 base: "Person".to_string(),
645 predicate: "is_adult".to_string(),
646 },
647 );
648
649 assert!(domain.validate(&table).is_err());
650 }
651
652 #[test]
653 fn test_registry_register() {
654 let mut registry = ComputedDomainRegistry::new();
655 let domain = ComputedDomain::new(
656 "Adults",
657 DomainComputation::Filter {
658 base: "Person".to_string(),
659 predicate: "is_adult".to_string(),
660 },
661 );
662 assert!(registry.register(domain).is_ok());
663 assert_eq!(registry.len(), 1);
664 }
665
666 #[test]
667 fn test_registry_duplicate() {
668 let mut registry = ComputedDomainRegistry::new();
669 let domain1 = ComputedDomain::new(
670 "Adults",
671 DomainComputation::Filter {
672 base: "Person".to_string(),
673 predicate: "is_adult".to_string(),
674 },
675 );
676 let domain2 = ComputedDomain::new(
677 "Adults",
678 DomainComputation::Filter {
679 base: "Person".to_string(),
680 predicate: "other".to_string(),
681 },
682 );
683 registry.register(domain1).unwrap();
684 assert!(registry.register(domain2).is_err());
685 }
686
687 #[test]
688 fn test_registry_get() {
689 let mut registry = ComputedDomainRegistry::new();
690 let domain = ComputedDomain::new(
691 "Adults",
692 DomainComputation::Filter {
693 base: "Person".to_string(),
694 predicate: "is_adult".to_string(),
695 },
696 );
697 registry.register(domain).unwrap();
698
699 assert!(registry.get("Adults").is_some());
700 assert!(registry.get("Unknown").is_none());
701 }
702
703 #[test]
704 fn test_registry_remove() {
705 let mut registry = ComputedDomainRegistry::new();
706 let domain = ComputedDomain::new(
707 "Adults",
708 DomainComputation::Filter {
709 base: "Person".to_string(),
710 predicate: "is_adult".to_string(),
711 },
712 );
713 registry.register(domain).unwrap();
714
715 assert!(registry.remove("Adults").is_some());
716 assert_eq!(registry.len(), 0);
717 }
718
719 #[test]
720 fn test_display() {
721 let domain = ComputedDomain::new(
722 "Adults",
723 DomainComputation::Filter {
724 base: "Person".to_string(),
725 predicate: "is_adult".to_string(),
726 },
727 );
728 let s = format!("{}", domain);
729 assert!(s.contains("Adults"));
730 assert!(s.contains(":="));
731 }
732}