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| {
256 table
257 .get_domain(d)
258 .expect("domain reference is valid")
259 .cardinality
260 })
261 .sum();
262 Ok((max_card, sum_card))
263 }
264 DomainComputation::Intersection { domains } => {
265 let min_card = domains
266 .iter()
267 .map(|d| {
268 table
269 .get_domain(d)
270 .map(|info| info.cardinality)
271 .ok_or_else(|| AdapterError::UnknownDomain(d.clone()))
272 })
273 .collect::<Result<Vec<_>, _>>()?
274 .into_iter()
275 .min()
276 .unwrap_or(0);
277 Ok((0, min_card))
278 }
279 DomainComputation::Difference { base, subtract } => {
280 let base_card = table
281 .get_domain(base)
282 .ok_or_else(|| AdapterError::UnknownDomain(base.clone()))?
283 .cardinality;
284 let sub_card = table
285 .get_domain(subtract)
286 .ok_or_else(|| AdapterError::UnknownDomain(subtract.clone()))?
287 .cardinality;
288 Ok((0, base_card.saturating_sub(sub_card)))
289 }
290 DomainComputation::Product { domains } => {
291 let product: usize = domains
292 .iter()
293 .map(|d| {
294 table
295 .get_domain(d)
296 .map(|info| info.cardinality)
297 .ok_or_else(|| AdapterError::UnknownDomain(d.clone()))
298 })
299 .collect::<Result<Vec<_>, _>>()?
300 .into_iter()
301 .product();
302 Ok((product, product))
303 }
304 DomainComputation::PowerSet { base } => {
305 let base_card = table
306 .get_domain(base)
307 .ok_or_else(|| AdapterError::UnknownDomain(base.clone()))?
308 .cardinality;
309 let power = 2_usize.pow(base_card as u32);
310 Ok((power, power))
311 }
312 DomainComputation::Projection { product, index: _ } => {
313 let prod_domain = table
314 .get_domain(product)
315 .ok_or_else(|| AdapterError::UnknownDomain(product.clone()))?;
316 Ok((0, prod_domain.cardinality))
318 }
319 DomainComputation::Custom { .. } => {
320 if let Some(est) = self.cardinality_estimate {
322 Ok((0, est))
323 } else {
324 Err(AdapterError::InvalidOperation(
325 "Custom domain computation requires cardinality estimate".to_string(),
326 ))
327 }
328 }
329 }
330 }
331
332 pub fn validate(&self, table: &SymbolTable) -> Result<(), AdapterError> {
334 match &self.computation {
335 DomainComputation::Filter { base, predicate } => {
336 if table.get_domain(base).is_none() {
337 return Err(AdapterError::UnknownDomain(base.clone()));
338 }
339 if table.get_predicate(predicate).is_none() {
340 return Err(AdapterError::UnknownPredicate(predicate.clone()));
341 }
342 }
343 DomainComputation::Union { domains } | DomainComputation::Intersection { domains } => {
344 for domain in domains {
345 if table.get_domain(domain).is_none() {
346 return Err(AdapterError::UnknownDomain(domain.clone()));
347 }
348 }
349 }
350 DomainComputation::Difference { base, subtract } => {
351 if table.get_domain(base).is_none() {
352 return Err(AdapterError::UnknownDomain(base.clone()));
353 }
354 if table.get_domain(subtract).is_none() {
355 return Err(AdapterError::UnknownDomain(subtract.clone()));
356 }
357 }
358 DomainComputation::Product { domains } => {
359 for domain in domains {
360 if table.get_domain(domain).is_none() {
361 return Err(AdapterError::UnknownDomain(domain.clone()));
362 }
363 }
364 }
365 DomainComputation::PowerSet { base } => {
366 if table.get_domain(base).is_none() {
367 return Err(AdapterError::UnknownDomain(base.clone()));
368 }
369 }
370 DomainComputation::Projection { product, .. } => {
371 if table.get_domain(product).is_none() {
372 return Err(AdapterError::UnknownDomain(product.clone()));
373 }
374 }
375 DomainComputation::Custom { .. } => {
376 }
378 }
379 Ok(())
380 }
381
382 pub fn materialize(&mut self) {
384 self.materialized = true;
385 }
386}
387
388impl fmt::Display for ComputedDomain {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 write!(f, "{} := {}", self.name, self.computation)
391 }
392}
393
394#[derive(Clone, Debug, Default)]
399pub struct ComputedDomainRegistry {
400 domains: HashMap<String, ComputedDomain>,
402}
403
404impl ComputedDomainRegistry {
405 pub fn new() -> Self {
407 Self {
408 domains: HashMap::new(),
409 }
410 }
411
412 pub fn register(&mut self, domain: ComputedDomain) -> Result<(), AdapterError> {
430 let name = domain.name().to_string();
431 if self.domains.contains_key(&name) {
432 return Err(AdapterError::DuplicateDomain(name));
433 }
434 self.domains.insert(name, domain);
435 Ok(())
436 }
437
438 pub fn get(&self, name: &str) -> Option<&ComputedDomain> {
440 self.domains.get(name)
441 }
442
443 pub fn get_mut(&mut self, name: &str) -> Option<&mut ComputedDomain> {
445 self.domains.get_mut(name)
446 }
447
448 pub fn list(&self) -> Vec<&ComputedDomain> {
450 self.domains.values().collect()
451 }
452
453 pub fn remove(&mut self, name: &str) -> Option<ComputedDomain> {
455 self.domains.remove(name)
456 }
457
458 pub fn validate_all(&self, table: &SymbolTable) -> Result<(), Vec<AdapterError>> {
460 let errors: Vec<_> = self
461 .domains
462 .values()
463 .filter_map(|domain| domain.validate(table).err())
464 .collect();
465
466 if errors.is_empty() {
467 Ok(())
468 } else {
469 Err(errors)
470 }
471 }
472
473 pub fn len(&self) -> usize {
475 self.domains.len()
476 }
477
478 pub fn is_empty(&self) -> bool {
480 self.domains.is_empty()
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use crate::{DomainInfo, PredicateInfo};
488
489 #[test]
490 fn test_filter_computation() {
491 let comp = DomainComputation::Filter {
492 base: "Person".to_string(),
493 predicate: "is_adult".to_string(),
494 };
495 assert!(comp.to_string().contains("Person"));
496 assert!(comp.to_string().contains("is_adult"));
497 }
498
499 #[test]
500 fn test_union_computation() {
501 let comp = DomainComputation::Union {
502 domains: vec!["A".to_string(), "B".to_string(), "C".to_string()],
503 };
504 assert_eq!(comp.to_string(), "A ∪ B ∪ C");
505 }
506
507 #[test]
508 fn test_intersection_computation() {
509 let comp = DomainComputation::Intersection {
510 domains: vec!["A".to_string(), "B".to_string()],
511 };
512 assert_eq!(comp.to_string(), "A ∩ B");
513 }
514
515 #[test]
516 fn test_difference_computation() {
517 let comp = DomainComputation::Difference {
518 base: "A".to_string(),
519 subtract: "B".to_string(),
520 };
521 assert_eq!(comp.to_string(), "A \\ B");
522 }
523
524 #[test]
525 fn test_product_computation() {
526 let comp = DomainComputation::Product {
527 domains: vec!["A".to_string(), "B".to_string()],
528 };
529 assert_eq!(comp.to_string(), "A × B");
530 }
531
532 #[test]
533 fn test_powerset_computation() {
534 let comp = DomainComputation::PowerSet {
535 base: "A".to_string(),
536 };
537 assert_eq!(comp.to_string(), "℘(A)");
538 }
539
540 #[test]
541 fn test_computed_domain_creation() {
542 let domain = ComputedDomain::new(
543 "Adults",
544 DomainComputation::Filter {
545 base: "Person".to_string(),
546 predicate: "is_adult".to_string(),
547 },
548 );
549 assert_eq!(domain.name(), "Adults");
550 assert!(!domain.is_materialized());
551 }
552
553 #[test]
554 fn test_cardinality_estimate() {
555 let domain = ComputedDomain::new(
556 "Adults",
557 DomainComputation::Filter {
558 base: "Person".to_string(),
559 predicate: "is_adult".to_string(),
560 },
561 )
562 .with_cardinality_estimate(750);
563 assert_eq!(domain.cardinality_estimate(), Some(750));
564 }
565
566 #[test]
567 fn test_cardinality_bounds_filter() {
568 let mut table = SymbolTable::new();
569 table
570 .add_domain(DomainInfo::new("Person", 1000))
571 .expect("unwrap");
572 table
573 .add_predicate(PredicateInfo::new("is_adult", vec!["Person".to_string()]))
574 .expect("unwrap");
575
576 let domain = ComputedDomain::new(
577 "Adults",
578 DomainComputation::Filter {
579 base: "Person".to_string(),
580 predicate: "is_adult".to_string(),
581 },
582 );
583
584 let (lower, upper) = domain.cardinality_bounds(&table).expect("unwrap");
585 assert_eq!(lower, 0);
586 assert_eq!(upper, 1000);
587 }
588
589 #[test]
590 fn test_cardinality_bounds_union() {
591 let mut table = SymbolTable::new();
592 table.add_domain(DomainInfo::new("A", 100)).expect("unwrap");
593 table.add_domain(DomainInfo::new("B", 200)).expect("unwrap");
594
595 let domain = ComputedDomain::new(
596 "AorB",
597 DomainComputation::Union {
598 domains: vec!["A".to_string(), "B".to_string()],
599 },
600 );
601
602 let (lower, upper) = domain.cardinality_bounds(&table).expect("unwrap");
603 assert_eq!(lower, 200); assert_eq!(upper, 300); }
606
607 #[test]
608 fn test_cardinality_bounds_product() {
609 let mut table = SymbolTable::new();
610 table.add_domain(DomainInfo::new("A", 10)).expect("unwrap");
611 table.add_domain(DomainInfo::new("B", 20)).expect("unwrap");
612
613 let domain = ComputedDomain::new(
614 "AxB",
615 DomainComputation::Product {
616 domains: vec!["A".to_string(), "B".to_string()],
617 },
618 );
619
620 let (lower, upper) = domain.cardinality_bounds(&table).expect("unwrap");
621 assert_eq!(lower, 200);
622 assert_eq!(upper, 200);
623 }
624
625 #[test]
626 fn test_validate_success() {
627 let mut table = SymbolTable::new();
628 table
629 .add_domain(DomainInfo::new("Person", 1000))
630 .expect("unwrap");
631 table
632 .add_predicate(PredicateInfo::new("is_adult", vec!["Person".to_string()]))
633 .expect("unwrap");
634
635 let domain = ComputedDomain::new(
636 "Adults",
637 DomainComputation::Filter {
638 base: "Person".to_string(),
639 predicate: "is_adult".to_string(),
640 },
641 );
642
643 assert!(domain.validate(&table).is_ok());
644 }
645
646 #[test]
647 fn test_validate_missing_domain() {
648 let table = SymbolTable::new();
649
650 let domain = ComputedDomain::new(
651 "Adults",
652 DomainComputation::Filter {
653 base: "Person".to_string(),
654 predicate: "is_adult".to_string(),
655 },
656 );
657
658 assert!(domain.validate(&table).is_err());
659 }
660
661 #[test]
662 fn test_registry_register() {
663 let mut registry = ComputedDomainRegistry::new();
664 let domain = ComputedDomain::new(
665 "Adults",
666 DomainComputation::Filter {
667 base: "Person".to_string(),
668 predicate: "is_adult".to_string(),
669 },
670 );
671 assert!(registry.register(domain).is_ok());
672 assert_eq!(registry.len(), 1);
673 }
674
675 #[test]
676 fn test_registry_duplicate() {
677 let mut registry = ComputedDomainRegistry::new();
678 let domain1 = ComputedDomain::new(
679 "Adults",
680 DomainComputation::Filter {
681 base: "Person".to_string(),
682 predicate: "is_adult".to_string(),
683 },
684 );
685 let domain2 = ComputedDomain::new(
686 "Adults",
687 DomainComputation::Filter {
688 base: "Person".to_string(),
689 predicate: "other".to_string(),
690 },
691 );
692 registry.register(domain1).expect("unwrap");
693 assert!(registry.register(domain2).is_err());
694 }
695
696 #[test]
697 fn test_registry_get() {
698 let mut registry = ComputedDomainRegistry::new();
699 let domain = ComputedDomain::new(
700 "Adults",
701 DomainComputation::Filter {
702 base: "Person".to_string(),
703 predicate: "is_adult".to_string(),
704 },
705 );
706 registry.register(domain).expect("unwrap");
707
708 assert!(registry.get("Adults").is_some());
709 assert!(registry.get("Unknown").is_none());
710 }
711
712 #[test]
713 fn test_registry_remove() {
714 let mut registry = ComputedDomainRegistry::new();
715 let domain = ComputedDomain::new(
716 "Adults",
717 DomainComputation::Filter {
718 base: "Person".to_string(),
719 predicate: "is_adult".to_string(),
720 },
721 );
722 registry.register(domain).expect("unwrap");
723
724 assert!(registry.remove("Adults").is_some());
725 assert_eq!(registry.len(), 0);
726 }
727
728 #[test]
729 fn test_display() {
730 let domain = ComputedDomain::new(
731 "Adults",
732 DomainComputation::Filter {
733 base: "Person".to_string(),
734 predicate: "is_adult".to_string(),
735 },
736 );
737 let s = format!("{}", domain);
738 assert!(s.contains("Adults"));
739 assert!(s.contains(":="));
740 }
741}