Skip to main content

solverforge_scoring/stream/
existence_target.rs

1/* Existence targets: turn a left filtered stream plus a right stream (direct
2or flattened) into a count-backed existence constraint.
3
4The relationship is a named pair of key adapters over the two operators'
5views. The left input is a `CollectionNode` over the left stream (whose
6membership is the stream's own filter); the right input is a `CollectionNode`
7over the right stream, or the parent collection with a `ParentChildKeys`
8adapter when the right stream is flattened. Right-side children are read
9straight from the solution and never become rows.
10*/
11
12use std::hash::Hash;
13use std::marker::PhantomData;
14
15use solverforge_core::score::Score;
16
17use super::collection_extract::{CollectionExtract, FlattenExtract};
18use super::existence_stream::{ExistenceMode, ExistsConstraintStream};
19use super::filter::UniFilter;
20use super::joiner::EqualJoiner;
21use super::relational::operator::{CollectionNode, ParentChildKeys, UnitKeys};
22use super::relational::view_plan::EntityKey;
23use super::uni_stream::UniConstraintStream;
24
25pub struct FlattenedCollectionTarget<S, P, B, EP, FP, Flatten, Sc>
26where
27    Sc: Score,
28{
29    pub(crate) right_stream: UniConstraintStream<S, P, EP, FP, Sc>,
30    pub(crate) flatten: Flatten,
31    pub(crate) _phantom: PhantomData<(fn() -> B, fn() -> Sc)>,
32}
33
34pub trait ExistenceTarget<S, A, EA, FA, Sc: Score>
35where
36    EA: CollectionExtract<S, Item = A>,
37    FA: UniFilter<S, A>,
38{
39    type Output;
40
41    fn apply(self, mode: ExistenceMode, extractor_a: EA, filter_a: FA) -> Self::Output;
42}
43
44/* Direct existence: `.if_exists((other_stream, equal_bi(ka, kb)))`.
45
46Both inputs are collections over their streams, so each input's membership is
47that stream's accumulated filter, and the relationship keys off the leaf views.
48*/
49impl<S, A, B, EA, FA, EB, FB, K, KA, KB, Mode, Sc> ExistenceTarget<S, A, EA, FA, Sc>
50    for (
51        UniConstraintStream<S, B, EB, FB, Sc>,
52        EqualJoiner<KA, KB, K, Mode>,
53    )
54where
55    S: Send + Sync + 'static,
56    A: Clone + Send + Sync + 'static,
57    B: Clone + Send + Sync + 'static,
58    EA: CollectionExtract<S, Item = A> + Send + Sync + 'static,
59    FA: UniFilter<S, A> + Send + Sync + 'static,
60    EB: CollectionExtract<S, Item = B> + Send + Sync + 'static,
61    FB: UniFilter<S, B> + Send + Sync + 'static,
62    K: Eq + Hash + Clone + Send + Sync + 'static,
63    KA: Fn(&A) -> K + Send + Sync + 'static,
64    KB: Fn(&B) -> K + Send + Sync + 'static,
65    Sc: Score + 'static,
66{
67    type Output = ExistsConstraintStream<
68        S,
69        A,
70        K,
71        CollectionNode<S, UniConstraintStream<S, A, EA, FA, Sc>>,
72        CollectionNode<S, UniConstraintStream<S, B, EB, FB, Sc>>,
73        EntityKey<KA>,
74        UnitKeys<EntityKey<KB>>,
75        Sc,
76    >;
77
78    fn apply(self, mode: ExistenceMode, extractor_a: EA, filter_a: FA) -> Self::Output {
79        let (right_stream, joiner) = self;
80        let (key_a, key_b) = joiner.into_keys();
81        let left_stream = UniConstraintStream::from_parts(extractor_a, filter_a);
82        let left = CollectionNode::new(left_stream, 0);
83        let right = CollectionNode::new(right_stream, 1);
84        ExistsConstraintStream::new(
85            mode,
86            left,
87            right,
88            EntityKey::new(key_a),
89            UnitKeys::new(EntityKey::new(key_b)),
90        )
91    }
92}
93
94/* Flattened existence: `.if_not_exists((stream.flattened(|p| ..), equal_bi(..)))`.
95
96The right input is the parent collection; the key adapter expands each
97parent's children in place from the solution, so no child rows are created.
98*/
99impl<S, A, P, B, EA, FA, EP, FP, K, KA, KB, Mode, Flatten, Sc> ExistenceTarget<S, A, EA, FA, Sc>
100    for (
101        FlattenedCollectionTarget<S, P, B, EP, FP, Flatten, Sc>,
102        EqualJoiner<KA, KB, K, Mode>,
103    )
104where
105    S: Send + Sync + 'static,
106    A: Clone + Send + Sync + 'static,
107    P: Clone + Send + Sync + 'static,
108    B: Clone + Send + Sync + 'static,
109    EA: CollectionExtract<S, Item = A> + Send + Sync + 'static,
110    FA: UniFilter<S, A> + Send + Sync + 'static,
111    EP: CollectionExtract<S, Item = P> + Send + Sync + 'static,
112    FP: UniFilter<S, P> + Send + Sync + 'static,
113    K: Eq + Hash + Clone + Send + Sync + 'static,
114    KA: Fn(&A) -> K + Send + Sync + 'static,
115    KB: Fn(&B) -> K + Send + Sync + 'static,
116    Flatten: FlattenExtract<P, Item = B> + Send + Sync + 'static,
117    Sc: Score + 'static,
118{
119    type Output = ExistsConstraintStream<
120        S,
121        A,
122        K,
123        CollectionNode<S, UniConstraintStream<S, A, EA, FA, Sc>>,
124        CollectionNode<S, UniConstraintStream<S, P, EP, FP, Sc>>,
125        EntityKey<KA>,
126        ParentChildKeys<KB, Flatten>,
127        Sc,
128    >;
129
130    fn apply(self, mode: ExistenceMode, extractor_a: EA, filter_a: FA) -> Self::Output {
131        let (target, joiner) = self;
132        let FlattenedCollectionTarget {
133            right_stream,
134            flatten,
135            ..
136        } = target;
137        let (key_a, key_b) = joiner.into_keys();
138        let left_stream = UniConstraintStream::from_parts(extractor_a, filter_a);
139        let left = CollectionNode::new(left_stream, 0);
140        let right = CollectionNode::new(right_stream, 1);
141        ExistsConstraintStream::new(
142            mode,
143            left,
144            right,
145            EntityKey::new(key_a),
146            ParentChildKeys::new(key_b, flatten),
147        )
148    }
149}