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<S, A, B, BiUnaryPlan<K, KA, KB>, E, EB, UniLeftBiFilter<F, B>, Sc>;
81
82    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
83        let (extractor_b, joiner) = self;
84        let (key_a, key_b) = joiner.into_keys();
85        let bi_filter = UniLeftBiFilter::new(filter_a);
86        Bi::from_condition(
87            extractor_a,
88            extractor_b,
89            BiUnaryPlan::new(key_a, key_b),
90            bi_filter,
91        )
92    }
93}
94
95macro_rules! impl_comparison_join_target {
96    ($joiner:ident, $less:expr, $inclusive:expr) => {
97        impl<S, A, B, E, F, EB, K, KA, KB, Sc> JoinTarget<S, A, E, F, Sc>
98            for (EB, $joiner<KA, KB, K>)
99        where
100            S: Send + Sync + 'static,
101            A: Clone + Send + Sync + 'static,
102            B: Clone + Send + Sync + 'static,
103            E: CollectionExtract<S, Item = A>,
104            F: UniFilter<S, A>,
105            EB: CollectionExtract<S, Item = B>,
106            K: Ord + Clone + Send + Sync + 'static,
107            KA: Fn(&A) -> K + Send + Sync + 'static,
108            KB: Fn(&B) -> K + Send + Sync + 'static,
109            Sc: Score + 'static,
110        {
111            type Output = Bi<
112                S,
113                A,
114                B,
115                ViewComparisonPlan<K, KA, KB, $less, $inclusive>,
116                E,
117                EB,
118                UniLeftBiFilter<F, B>,
119                Sc,
120            >;
121
122            fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
123                let (extractor_b, joiner) = self;
124                let (key_a, key_b) = joiner.into_keys();
125                let bi_filter = UniLeftBiFilter::new(filter_a);
126                Bi::from_condition(
127                    extractor_a,
128                    extractor_b,
129                    ViewComparisonPlan::<K, KA, KB, $less, $inclusive>::new(key_a, key_b),
130                    bi_filter,
131                )
132            }
133        }
134    };
135}
136
137// Comparison conditions: all four directions, strict and inclusive. Authored
138// over entities, executed over leaf views by `ViewComparisonPlan`.
139impl_comparison_join_target!(LessThanJoiner, true, false);
140impl_comparison_join_target!(LessThanOrEqualJoiner, true, true);
141impl_comparison_join_target!(GreaterThanJoiner, false, false);
142impl_comparison_join_target!(GreaterThanOrEqualJoiner, false, true);
143
144// Interval overlap over entity-authored bounds, executed over leaf views.
145impl<S, A, B, E, F, EB, K, SA, EA, SB, EBd, Sc> JoinTarget<S, A, E, F, Sc>
146    for (EB, OverlappingJoiner<SA, EA, SB, EBd, K>)
147where
148    S: Send + Sync + 'static,
149    A: Clone + Send + Sync + 'static,
150    B: Clone + Send + Sync + 'static,
151    E: CollectionExtract<S, Item = A>,
152    F: UniFilter<S, A>,
153    EB: CollectionExtract<S, Item = B>,
154    K: Ord + Clone + Send + Sync + 'static,
155    SA: Fn(&A) -> K + Send + Sync + 'static,
156    EA: Fn(&A) -> K + Send + Sync + 'static,
157    SB: Fn(&B) -> K + Send + Sync + 'static,
158    EBd: Fn(&B) -> K + Send + Sync + 'static,
159    Sc: Score + 'static,
160{
161    type Output =
162        Bi<S, A, B, ViewOverlapPlan<K, SA, EA, SB, EBd>, E, EB, UniLeftBiFilter<F, B>, Sc>;
163
164    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
165        let (extractor_b, joiner) = self;
166        let (start_a, end_a, start_b, end_b) = joiner.into_bounds();
167        let bi_filter = UniLeftBiFilter::new(filter_a);
168        Bi::from_condition(
169            extractor_a,
170            extractor_b,
171            ViewOverlapPlan::new(start_a, end_a, start_b, end_b),
172            bi_filter,
173        )
174    }
175}
176
177// Composed conjunction: each operand converts to its view plan, then the two
178// compose into one concrete `AndJoiner` plan. No dynamic condition list.
179impl<S, A, B, E, F, EB, J1, J2, Sc> JoinTarget<S, A, E, F, Sc> for (EB, AndJoiner<J1, J2>)
180where
181    S: Send + Sync + 'static,
182    A: Clone + Send + Sync + 'static,
183    B: Clone + Send + Sync + 'static,
184    E: CollectionExtract<S, Item = A>,
185    F: UniFilter<S, A>,
186    EB: CollectionExtract<S, Item = B>,
187    J1: ToViewPlan<A, B> + 'static,
188    J2: ToViewPlan<A, B> + 'static,
189    AndJoiner<J1::ViewPlan, J2::ViewPlan>: CompileCondition<Plan: IndexedPlan> + 'static,
190    for<'a> <AndJoiner<J1::ViewPlan, J2::ViewPlan> as CompileCondition>::Plan:
191        ExecutablePlan<Leaf<'a, A>, Leaf<'a, B>>,
192    Sc: Score + 'static,
193{
194    type Output = Bi<
195        S,
196        A,
197        B,
198        <AndJoiner<J1::ViewPlan, J2::ViewPlan> as CompileCondition>::Plan,
199        E,
200        EB,
201        UniLeftBiFilter<F, B>,
202        Sc,
203    >;
204
205    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
206        let (extractor_b, joiner) = self;
207        let (first, second) = joiner.into_parts();
208        let composed = AndJoiner::new(first.into_view_plan(), second.into_view_plan());
209        let bi_filter = UniLeftBiFilter::new(filter_a);
210        Bi::from_condition(extractor_a, extractor_b, composed.compile(), bi_filter)
211    }
212}
213
214// Predicate cross-join: `.join((other_stream, |a, b| predicate))` — an
215// explicit opposite-input scan under the exact predicate. The relationship
216// compiles to `FilteringJoiner`, so no synthetic constant equality key is
217// ever built for a non-indexable condition.
218impl<S, A, B, E, F, EB, FB, P, Sc> JoinTarget<S, A, E, F, Sc>
219    for (UniConstraintStream<S, B, EB, FB, Sc>, P)
220where
221    S: Send + Sync + 'static,
222    A: Clone + Send + Sync + 'static,
223    B: Clone + Send + Sync + 'static,
224    E: CollectionExtract<S, Item = A>,
225    F: UniFilter<S, A>,
226    EB: CollectionExtract<S, Item = B>,
227    FB: UniFilter<S, B>,
228    P: Fn(&A, &B) -> bool + Send + Sync + 'static,
229    Sc: Score + 'static,
230{
231    type Output = Bi<S, A, B, BiPredicatePlan<P>, E, EB, UniPairFilter<F, FB>, Sc>;
232
233    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
234        let (other_stream, predicate) = self;
235        let (extractor_b, filter_b) = other_stream.into_parts();
236        let combined_filter = UniPairFilter::new(filter_a, filter_b);
237        Bi::from_condition(
238            extractor_a,
239            extractor_b,
240            BiPredicatePlan::new(predicate),
241            combined_filter,
242        )
243    }
244}
245
246/* Converts an entity-authored condition into the leaf-view plan the operator
247tree executes. Used by the conjunction impl to compose operands; the
248top-level equality, comparison, and overlap impls build their plans directly
249so the equality path keeps producing `BiUnaryPlan`. */
250pub trait ToViewPlan<A, B> {
251    type ViewPlan;
252    fn into_view_plan(self) -> Self::ViewPlan;
253}
254
255impl<A, B, K, KA, KB, Mode> ToViewPlan<A, B> for EqualJoiner<KA, KB, K, Mode>
256where
257    K: Eq + Hash + Clone,
258    KA: Fn(&A) -> K + Send + Sync,
259    KB: Fn(&B) -> K + Send + Sync,
260{
261    type ViewPlan = ViewEqualPlan<K, EntityKey<KA>, EntityKey<KB>>;
262    fn into_view_plan(self) -> Self::ViewPlan {
263        let (key_a, key_b) = self.into_keys();
264        ViewEqualPlan::new(EntityKey::new(key_a), EntityKey::new(key_b))
265    }
266}
267
268macro_rules! impl_to_view_plan_comparison {
269    ($joiner:ident, $less:expr, $inclusive:expr) => {
270        impl<A, B, K, KA, KB> ToViewPlan<A, B> for $joiner<KA, KB, K>
271        where
272            K: Ord + Clone,
273            KA: Fn(&A) -> K + Send + Sync,
274            KB: Fn(&B) -> K + Send + Sync,
275        {
276            type ViewPlan = ViewComparisonPlan<K, KA, KB, $less, $inclusive>;
277            fn into_view_plan(self) -> Self::ViewPlan {
278                let (key_a, key_b) = self.into_keys();
279                ViewComparisonPlan::new(key_a, key_b)
280            }
281        }
282    };
283}
284impl_to_view_plan_comparison!(LessThanJoiner, true, false);
285impl_to_view_plan_comparison!(LessThanOrEqualJoiner, true, true);
286impl_to_view_plan_comparison!(GreaterThanJoiner, false, false);
287impl_to_view_plan_comparison!(GreaterThanOrEqualJoiner, false, true);
288
289impl<A, B, K, SA, EA, SB, EBd> ToViewPlan<A, B> for OverlappingJoiner<SA, EA, SB, EBd, K>
290where
291    K: Ord + Clone,
292    SA: Fn(&A) -> K + Send + Sync,
293    EA: Fn(&A) -> K + Send + Sync,
294    SB: Fn(&B) -> K + Send + Sync,
295    EBd: Fn(&B) -> K + Send + Sync,
296{
297    type ViewPlan = ViewOverlapPlan<K, SA, EA, SB, EBd>;
298    fn into_view_plan(self) -> Self::ViewPlan {
299        let (start_a, end_a, start_b, end_b) = self.into_bounds();
300        ViewOverlapPlan::new(start_a, end_a, start_b, end_b)
301    }
302}
303
304impl<A, B, J1, J2> ToViewPlan<A, B> for AndJoiner<J1, J2>
305where
306    J1: ToViewPlan<A, B>,
307    J2: ToViewPlan<A, B>,
308{
309    type ViewPlan = AndJoiner<J1::ViewPlan, J2::ViewPlan>;
310    fn into_view_plan(self) -> Self::ViewPlan {
311        let (first, second) = self.into_parts();
312        AndJoiner::new(first.into_view_plan(), second.into_view_plan())
313    }
314}