Skip to main content

solverforge_scoring/stream/
join_target.rs

1/* JoinTarget trait for single `.join()` dispatch on UniConstraintStream.
2
3Three impls cover all join patterns:
41. `EqualJoiner<KA, KA, K>` — self-join, returns `BiConstraintStream`
52. `(EB, EqualJoiner<KA, KB, K>)` — keyed cross-join, returns `Bi`
63. `(UniConstraintStream<...>, P)` — predicate cross-join, returns `Bi`
7*/
8
9use std::hash::Hash;
10
11use solverforge_core::score::Score;
12
13use super::bi_stream::BiConstraintStream;
14use super::collection_extract::CollectionExtract;
15use super::cross_bi_stream::{
16    Bi, BiPredicatePlan, BiUnaryPlan, ViewComparisonPlan, ViewOverlapPlan,
17};
18use super::filter::{UniBiFilter, UniFilter, UniLeftBiFilter, UniPairFilter};
19use super::joiner::plan::{CompileCondition, ExecutablePlan, IndexedPlan};
20use super::joiner::{
21    AndJoiner, EqualJoiner, GreaterThanJoiner, GreaterThanOrEqualJoiner, LessThanJoiner,
22    LessThanOrEqualJoiner, OverlappingJoiner, Symmetric,
23};
24use super::key_extract::EntityKeyAdapter;
25use super::relational::view_plan::{EntityKey, ViewEqualPlan};
26use super::relational::Leaf;
27use super::UniConstraintStream;
28
29/* Trait for single `.join()` dispatch.
30
31`E` is the extractor type of the left stream.
32`F` is the filter type of the left stream.
33Implementors consume `self` and receive the left stream's extractor and filter,
34producing the appropriate cross-stream type.
35*/
36pub trait JoinTarget<S, A, E, F, Sc: Score> {
37    // The resulting constraint stream type.
38    type Output;
39
40    // Applies the join, consuming both the target and the left stream's components.
41    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output;
42}
43
44// Self-join: `.join(equal(|a: &A| a.key))` — pairs same-collection entities.
45impl<S, A, E, F, K, KA, Sc> JoinTarget<S, A, E, F, Sc> for EqualJoiner<KA, KA, K, Symmetric>
46where
47    S: Send + Sync + 'static,
48    A: Clone + Hash + PartialEq + Send + Sync + 'static,
49    E: CollectionExtract<S, Item = A>,
50    F: UniFilter<S, A>,
51    K: Eq + Hash + Clone + Send + Sync,
52    KA: Fn(&A) -> K + Send + Sync,
53    Sc: Score + 'static,
54{
55    type Output = BiConstraintStream<S, A, K, E, EntityKeyAdapter<KA>, UniBiFilter<F, A>, Sc>;
56
57    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
58        let (key_fn, _) = self.into_keys();
59        let key_extractor = EntityKeyAdapter::new(key_fn);
60        let bi_filter = UniBiFilter::new(filter_a);
61        BiConstraintStream::new_self_join_with_filter(extractor_a, key_extractor, bi_filter)
62    }
63}
64
65// Keyed cross-join: `.join((extractor_b, equal_bi(ka, kb)))` — pairs two collections by key.
66impl<S, A, B, E, F, EB, K, KA, KB, Mode, Sc> JoinTarget<S, A, E, F, Sc>
67    for (EB, EqualJoiner<KA, KB, K, Mode>)
68where
69    S: Send + Sync + 'static,
70    A: Clone + Send + Sync + 'static,
71    B: Clone + Send + Sync + 'static,
72    E: CollectionExtract<S, Item = A>,
73    F: UniFilter<S, A>,
74    EB: CollectionExtract<S, Item = B>,
75    K: Eq + Hash + Clone + Send + Sync,
76    KA: Fn(&A) -> K + Send + Sync,
77    KB: Fn(&B) -> K + Send + Sync,
78    Sc: Score + 'static,
79{
80    type Output = Bi<
81        S,
82        A,
83        B,
84        BiUnaryPlan<K, KA, KB>,
85        super::collection_extract::FilteredExtract<E, F>,
86        EB,
87        super::filter::TrueFilter,
88        Sc,
89    >;
90
91    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
92        let (extractor_b, joiner) = self;
93        let (key_a, key_b) = joiner.into_keys();
94        Bi::from_condition(
95            super::collection_extract::FilteredExtract::new(extractor_a, filter_a),
96            extractor_b,
97            BiUnaryPlan::new(key_a, key_b),
98            super::filter::TrueFilter,
99        )
100    }
101}
102
103macro_rules! impl_comparison_join_target {
104    ($joiner:ident, $less:expr, $inclusive:expr) => {
105        impl<S, A, B, E, F, EB, K, KA, KB, Sc> JoinTarget<S, A, E, F, Sc>
106            for (EB, $joiner<KA, KB, K>)
107        where
108            S: Send + Sync + 'static,
109            A: Clone + Send + Sync + 'static,
110            B: Clone + Send + Sync + 'static,
111            E: CollectionExtract<S, Item = A>,
112            F: UniFilter<S, A>,
113            EB: CollectionExtract<S, Item = B>,
114            K: Ord + Clone + Send + Sync + 'static,
115            KA: Fn(&A) -> K + Send + Sync + 'static,
116            KB: Fn(&B) -> K + Send + Sync + 'static,
117            Sc: Score + 'static,
118        {
119            type Output = Bi<
120                S,
121                A,
122                B,
123                ViewComparisonPlan<K, KA, KB, $less, $inclusive>,
124                E,
125                EB,
126                UniLeftBiFilter<F, B>,
127                Sc,
128            >;
129
130            fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
131                let (extractor_b, joiner) = self;
132                let (key_a, key_b) = joiner.into_keys();
133                let bi_filter = UniLeftBiFilter::new(filter_a);
134                Bi::from_condition(
135                    extractor_a,
136                    extractor_b,
137                    ViewComparisonPlan::<K, KA, KB, $less, $inclusive>::new(key_a, key_b),
138                    bi_filter,
139                )
140            }
141        }
142    };
143}
144
145// Comparison conditions: all four directions, strict and inclusive. Authored
146// over entities, executed over leaf views by `ViewComparisonPlan`.
147impl_comparison_join_target!(LessThanJoiner, true, false);
148impl_comparison_join_target!(LessThanOrEqualJoiner, true, true);
149impl_comparison_join_target!(GreaterThanJoiner, false, false);
150impl_comparison_join_target!(GreaterThanOrEqualJoiner, false, true);
151
152// Interval overlap over entity-authored bounds, executed over leaf views.
153impl<S, A, B, E, F, EB, K, SA, EA, SB, EBd, Sc> JoinTarget<S, A, E, F, Sc>
154    for (EB, OverlappingJoiner<SA, EA, SB, EBd, K>)
155where
156    S: Send + Sync + 'static,
157    A: Clone + Send + Sync + 'static,
158    B: Clone + Send + Sync + 'static,
159    E: CollectionExtract<S, Item = A>,
160    F: UniFilter<S, A>,
161    EB: CollectionExtract<S, Item = B>,
162    K: Ord + Clone + Send + Sync + 'static,
163    SA: Fn(&A) -> K + Send + Sync + 'static,
164    EA: Fn(&A) -> K + Send + Sync + 'static,
165    SB: Fn(&B) -> K + Send + Sync + 'static,
166    EBd: Fn(&B) -> K + Send + Sync + 'static,
167    Sc: Score + 'static,
168{
169    type Output =
170        Bi<S, A, B, ViewOverlapPlan<K, SA, EA, SB, EBd>, E, EB, UniLeftBiFilter<F, B>, Sc>;
171
172    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
173        let (extractor_b, joiner) = self;
174        let (start_a, end_a, start_b, end_b) = joiner.into_bounds();
175        let bi_filter = UniLeftBiFilter::new(filter_a);
176        Bi::from_condition(
177            extractor_a,
178            extractor_b,
179            ViewOverlapPlan::new(start_a, end_a, start_b, end_b),
180            bi_filter,
181        )
182    }
183}
184
185// Composed conjunction: each operand converts to its view plan, then the two
186// compose into one concrete `AndJoiner` plan. No dynamic condition list.
187impl<S, A, B, E, F, EB, J1, J2, Sc> JoinTarget<S, A, E, F, Sc> for (EB, AndJoiner<J1, J2>)
188where
189    S: Send + Sync + 'static,
190    A: Clone + Send + Sync + 'static,
191    B: Clone + Send + Sync + 'static,
192    E: CollectionExtract<S, Item = A>,
193    F: UniFilter<S, A>,
194    EB: CollectionExtract<S, Item = B>,
195    J1: ToViewPlan<A, B> + 'static,
196    J2: ToViewPlan<A, B> + 'static,
197    AndJoiner<J1::ViewPlan, J2::ViewPlan>: CompileCondition<Plan: IndexedPlan> + 'static,
198    for<'a> <AndJoiner<J1::ViewPlan, J2::ViewPlan> as CompileCondition>::Plan:
199        ExecutablePlan<Leaf<'a, A>, Leaf<'a, B>>,
200    Sc: Score + 'static,
201{
202    type Output = Bi<
203        S,
204        A,
205        B,
206        <AndJoiner<J1::ViewPlan, J2::ViewPlan> as CompileCondition>::Plan,
207        E,
208        EB,
209        UniLeftBiFilter<F, B>,
210        Sc,
211    >;
212
213    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
214        let (extractor_b, joiner) = self;
215        let (first, second) = joiner.into_parts();
216        let composed = AndJoiner::new(first.into_view_plan(), second.into_view_plan());
217        let bi_filter = UniLeftBiFilter::new(filter_a);
218        Bi::from_condition(extractor_a, extractor_b, composed.compile(), bi_filter)
219    }
220}
221
222// Predicate cross-join: `.join((other_stream, |a, b| predicate))` — an
223// explicit opposite-input scan under the exact predicate. The relationship
224// compiles to `FilteringJoiner`, so no synthetic constant equality key is
225// ever built for a non-indexable condition.
226impl<S, A, B, E, F, EB, FB, P, Sc> JoinTarget<S, A, E, F, Sc>
227    for (UniConstraintStream<S, B, EB, FB, Sc>, P)
228where
229    S: Send + Sync + 'static,
230    A: Clone + Send + Sync + 'static,
231    B: Clone + Send + Sync + 'static,
232    E: CollectionExtract<S, Item = A>,
233    F: UniFilter<S, A>,
234    EB: CollectionExtract<S, Item = B>,
235    FB: UniFilter<S, B>,
236    P: Fn(&A, &B) -> bool + Send + Sync + 'static,
237    Sc: Score + 'static,
238{
239    type Output = Bi<S, A, B, BiPredicatePlan<P>, E, EB, UniPairFilter<F, FB>, Sc>;
240
241    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
242        let (other_stream, predicate) = self;
243        let (extractor_b, filter_b) = other_stream.into_parts();
244        let combined_filter = UniPairFilter::new(filter_a, filter_b);
245        Bi::from_condition(
246            extractor_a,
247            extractor_b,
248            BiPredicatePlan::new(predicate),
249            combined_filter,
250        )
251    }
252}
253
254/* Converts an entity-authored condition into the leaf-view plan the operator
255tree executes. Used by the conjunction impl to compose operands; the
256top-level equality, comparison, and overlap impls build their plans directly
257so the equality path keeps producing `BiUnaryPlan`. */
258pub trait ToViewPlan<A, B> {
259    type ViewPlan;
260    fn into_view_plan(self) -> Self::ViewPlan;
261}
262
263impl<A, B, K, KA, KB, Mode> ToViewPlan<A, B> for EqualJoiner<KA, KB, K, Mode>
264where
265    K: Eq + Hash + Clone,
266    KA: Fn(&A) -> K + Send + Sync,
267    KB: Fn(&B) -> K + Send + Sync,
268{
269    type ViewPlan = ViewEqualPlan<K, EntityKey<KA>, EntityKey<KB>>;
270    fn into_view_plan(self) -> Self::ViewPlan {
271        let (key_a, key_b) = self.into_keys();
272        ViewEqualPlan::new(EntityKey::new(key_a), EntityKey::new(key_b))
273    }
274}
275
276macro_rules! impl_to_view_plan_comparison {
277    ($joiner:ident, $less:expr, $inclusive:expr) => {
278        impl<A, B, K, KA, KB> ToViewPlan<A, B> for $joiner<KA, KB, K>
279        where
280            K: Ord + Clone,
281            KA: Fn(&A) -> K + Send + Sync,
282            KB: Fn(&B) -> K + Send + Sync,
283        {
284            type ViewPlan = ViewComparisonPlan<K, KA, KB, $less, $inclusive>;
285            fn into_view_plan(self) -> Self::ViewPlan {
286                let (key_a, key_b) = self.into_keys();
287                ViewComparisonPlan::new(key_a, key_b)
288            }
289        }
290    };
291}
292impl_to_view_plan_comparison!(LessThanJoiner, true, false);
293impl_to_view_plan_comparison!(LessThanOrEqualJoiner, true, true);
294impl_to_view_plan_comparison!(GreaterThanJoiner, false, false);
295impl_to_view_plan_comparison!(GreaterThanOrEqualJoiner, false, true);
296
297impl<A, B, K, SA, EA, SB, EBd> ToViewPlan<A, B> for OverlappingJoiner<SA, EA, SB, EBd, K>
298where
299    K: Ord + Clone,
300    SA: Fn(&A) -> K + Send + Sync,
301    EA: Fn(&A) -> K + Send + Sync,
302    SB: Fn(&B) -> K + Send + Sync,
303    EBd: Fn(&B) -> K + Send + Sync,
304{
305    type ViewPlan = ViewOverlapPlan<K, SA, EA, SB, EBd>;
306    fn into_view_plan(self) -> Self::ViewPlan {
307        let (start_a, end_a, start_b, end_b) = self.into_bounds();
308        ViewOverlapPlan::new(start_a, end_a, start_b, end_b)
309    }
310}
311
312impl<A, B, J1, J2> ToViewPlan<A, B> for AndJoiner<J1, J2>
313where
314    J1: ToViewPlan<A, B>,
315    J2: ToViewPlan<A, B>,
316{
317    type ViewPlan = AndJoiner<J1::ViewPlan, J2::ViewPlan>;
318    fn into_view_plan(self) -> Self::ViewPlan {
319        let (first, second) = self.into_parts();
320        AndJoiner::new(first.into_view_plan(), second.into_view_plan())
321    }
322}