Skip to main content

polars_ops/frame/join/hash_join/
single_keys_dispatch.rs

1use arrow::array::PrimitiveArray;
2use polars_core::chunked_array::ops::row_encode::{
3    encode_rows_unordered, encode_rows_vertical_par_unordered_broadcast_nulls,
4};
5use polars_core::series::BitRepr;
6use polars_core::utils::split;
7use polars_core::with_match_physical_float_polars_type;
8use polars_utils::aliases::PlRandomState;
9use polars_utils::hashing::DirtyHash;
10use polars_utils::nulls::IsNull;
11use polars_utils::total_ord::{ToTotalOrd, TotalEq, TotalHash};
12
13use super::*;
14use crate::series::SeriesSealed;
15
16pub trait SeriesJoin: SeriesSealed + Sized {
17    #[doc(hidden)]
18    fn hash_join_left(
19        &self,
20        other: &Series,
21        validate: JoinValidation,
22        nulls_equal: bool,
23    ) -> PolarsResult<LeftJoinIds> {
24        let s_self = self.as_series();
25        let (lhs, rhs) = (s_self.to_physical_repr(), other.to_physical_repr());
26        validate.validate_probe(&lhs, &rhs, false, nulls_equal)?;
27
28        let lhs_dtype = lhs.dtype();
29        let rhs_dtype = rhs.dtype();
30
31        use DataType as T;
32        match lhs_dtype {
33            T::String | T::Binary => {
34                let lhs = lhs.cast(&T::Binary).unwrap();
35                let rhs = rhs.cast(&T::Binary).unwrap();
36                let lhs = lhs.binary().unwrap();
37                let rhs = rhs.binary().unwrap();
38                let (lhs, rhs, _, _) = prepare_binary::<BinaryType>(lhs, rhs, false);
39                let lhs = lhs.iter().map(|v| v.as_slice()).collect::<Vec<_>>();
40                let rhs = rhs.iter().map(|v| v.as_slice()).collect::<Vec<_>>();
41                let build_null_count = other.null_count();
42                hash_join_tuples_left(
43                    lhs,
44                    rhs,
45                    None,
46                    None,
47                    validate,
48                    nulls_equal,
49                    build_null_count,
50                )
51            },
52            T::BinaryOffset => {
53                let lhs = lhs.binary_offset().unwrap();
54                let rhs = rhs.binary_offset().unwrap();
55                let (lhs, rhs, _, _) = prepare_binary::<BinaryOffsetType>(lhs, rhs, false);
56                // Take slices so that vecs are not copied
57                let lhs = lhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
58                let rhs = rhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
59                let build_null_count = other.null_count();
60                hash_join_tuples_left(
61                    lhs,
62                    rhs,
63                    None,
64                    None,
65                    validate,
66                    nulls_equal,
67                    build_null_count,
68                )
69            },
70            T::List(_) => {
71                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
72                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
73                lhs.hash_join_left(rhs, validate, nulls_equal)
74            },
75            #[cfg(feature = "dtype-array")]
76            T::Array(_, _) => {
77                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
78                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
79                lhs.hash_join_left(rhs, validate, nulls_equal)
80            },
81            #[cfg(feature = "dtype-struct")]
82            T::Struct(_) => {
83                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
84                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
85                lhs.hash_join_left(rhs, validate, nulls_equal)
86            },
87            x if x.is_float() => {
88                with_match_physical_float_polars_type!(lhs.dtype(), |$T| {
89                    let lhs: &ChunkedArray<$T> = lhs.as_ref().as_ref().as_ref();
90                    let rhs: &ChunkedArray<$T> = rhs.as_ref().as_ref().as_ref();
91                    num_group_join_left(lhs, rhs, validate, nulls_equal)
92                })
93            },
94            _ => {
95                let lhs = s_self.bit_repr();
96                let rhs = other.bit_repr();
97
98                let (Some(lhs), Some(rhs)) = (lhs, rhs) else {
99                    polars_bail!(nyi = "Hash Left Join between {lhs_dtype} and {rhs_dtype}");
100                };
101
102                use BitRepr as B;
103                match (lhs, rhs) {
104                    (B::U8(lhs), B::U8(rhs)) => {
105                        num_group_join_left(&lhs, &rhs, validate, nulls_equal)
106                    },
107                    (B::U16(lhs), B::U16(rhs)) => {
108                        num_group_join_left(&lhs, &rhs, validate, nulls_equal)
109                    },
110                    (B::U32(lhs), B::U32(rhs)) => {
111                        num_group_join_left(&lhs, &rhs, validate, nulls_equal)
112                    },
113                    (B::U64(lhs), B::U64(rhs)) => {
114                        num_group_join_left(&lhs, &rhs, validate, nulls_equal)
115                    },
116                    #[cfg(feature = "dtype-u128")]
117                    (B::U128(lhs), B::U128(rhs)) => {
118                        num_group_join_left(&lhs, &rhs, validate, nulls_equal)
119                    },
120                    _ => {
121                        polars_bail!(
122                            nyi = "Mismatch bit repr Hash Left Join between {lhs_dtype} and {rhs_dtype}",
123                        );
124                    },
125                }
126            },
127        }
128    }
129
130    #[cfg(feature = "semi_anti_join")]
131    fn hash_join_semi_anti(
132        &self,
133        other: &Series,
134        anti: bool,
135        nulls_equal: bool,
136    ) -> PolarsResult<Vec<IdxSize>> {
137        let s_self = self.as_series();
138        let (lhs, rhs) = (s_self.to_physical_repr(), other.to_physical_repr());
139
140        let lhs_dtype = lhs.dtype();
141        let rhs_dtype = rhs.dtype();
142
143        use DataType as T;
144        Ok(match lhs_dtype {
145            T::String | T::Binary => {
146                let lhs = lhs.cast(&T::Binary).unwrap();
147                let rhs = rhs.cast(&T::Binary).unwrap();
148                let lhs = lhs.binary().unwrap();
149                let rhs = rhs.binary().unwrap();
150                let (lhs, rhs, _, _) = prepare_binary::<BinaryType>(lhs, rhs, false);
151                // Take slices so that vecs are not copied
152                let lhs = lhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
153                let rhs = rhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
154                if anti {
155                    hash_join_tuples_left_anti(lhs, rhs, nulls_equal)
156                } else {
157                    hash_join_tuples_left_semi(lhs, rhs, nulls_equal)
158                }
159            },
160            T::BinaryOffset => {
161                let lhs = lhs.binary_offset().unwrap();
162                let rhs = rhs.binary_offset().unwrap();
163                let (lhs, rhs, _, _) = prepare_binary::<BinaryOffsetType>(lhs, rhs, false);
164                // Take slices so that vecs are not copied
165                let lhs = lhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
166                let rhs = rhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
167                if anti {
168                    hash_join_tuples_left_anti(lhs, rhs, nulls_equal)
169                } else {
170                    hash_join_tuples_left_semi(lhs, rhs, nulls_equal)
171                }
172            },
173            T::List(_) => {
174                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
175                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
176                lhs.hash_join_semi_anti(rhs, anti, nulls_equal)?
177            },
178            #[cfg(feature = "dtype-array")]
179            T::Array(_, _) => {
180                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
181                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
182                lhs.hash_join_semi_anti(rhs, anti, nulls_equal)?
183            },
184            #[cfg(feature = "dtype-struct")]
185            T::Struct(_) => {
186                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
187                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
188                lhs.hash_join_semi_anti(rhs, anti, nulls_equal)?
189            },
190            x if x.is_float() => {
191                with_match_physical_float_polars_type!(lhs.dtype(), |$T| {
192                    let lhs: &ChunkedArray<$T> = lhs.as_ref().as_ref().as_ref();
193                    let rhs: &ChunkedArray<$T> = rhs.as_ref().as_ref().as_ref();
194                    num_group_join_anti_semi(lhs, rhs, anti, nulls_equal)
195                })
196            },
197            _ => {
198                let lhs = s_self.bit_repr();
199                let rhs = other.bit_repr();
200
201                let (Some(lhs), Some(rhs)) = (lhs, rhs) else {
202                    polars_bail!(nyi = "Hash Semi-Anti Join between {lhs_dtype} and {rhs_dtype}");
203                };
204
205                use BitRepr as B;
206                match (lhs, rhs) {
207                    (B::U8(lhs), B::U8(rhs)) => {
208                        num_group_join_anti_semi(&lhs, &rhs, anti, nulls_equal)
209                    },
210                    (B::U16(lhs), B::U16(rhs)) => {
211                        num_group_join_anti_semi(&lhs, &rhs, anti, nulls_equal)
212                    },
213                    (B::U32(lhs), B::U32(rhs)) => {
214                        num_group_join_anti_semi(&lhs, &rhs, anti, nulls_equal)
215                    },
216                    (B::U64(lhs), B::U64(rhs)) => {
217                        num_group_join_anti_semi(&lhs, &rhs, anti, nulls_equal)
218                    },
219                    #[cfg(feature = "dtype-u128")]
220                    (B::U128(lhs), B::U128(rhs)) => {
221                        num_group_join_anti_semi(&lhs, &rhs, anti, nulls_equal)
222                    },
223                    _ => {
224                        polars_bail!(
225                            nyi = "Mismatch bit repr Hash Semi-Anti Join between {lhs_dtype} and {rhs_dtype}",
226                        );
227                    },
228                }
229            },
230        })
231    }
232
233    // returns the join tuples and whether or not the lhs tuples are sorted
234    fn hash_join_inner(
235        &self,
236        other: &Series,
237        validate: JoinValidation,
238        nulls_equal: bool,
239    ) -> PolarsResult<(InnerJoinIds, bool)> {
240        let s_self = self.as_series();
241        let (lhs, rhs) = (s_self.to_physical_repr(), other.to_physical_repr());
242        validate.validate_probe(&lhs, &rhs, true, nulls_equal)?;
243
244        let lhs_dtype = lhs.dtype();
245        let rhs_dtype = rhs.dtype();
246
247        use DataType as T;
248        match lhs_dtype {
249            T::String | T::Binary => {
250                let lhs = lhs.cast(&T::Binary).unwrap();
251                let rhs = rhs.cast(&T::Binary).unwrap();
252                let lhs = lhs.binary().unwrap();
253                let rhs = rhs.binary().unwrap();
254                let (lhs, rhs, swapped, _) = prepare_binary::<BinaryType>(lhs, rhs, true);
255                // Take slices so that vecs are not copied
256                let lhs = lhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
257                let rhs = rhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
258                let build_null_count = if swapped {
259                    s_self.null_count()
260                } else {
261                    other.null_count()
262                };
263                Ok((
264                    hash_join_tuples_inner(
265                        lhs,
266                        rhs,
267                        swapped,
268                        validate,
269                        nulls_equal,
270                        build_null_count,
271                    )?,
272                    !swapped,
273                ))
274            },
275            T::BinaryOffset => {
276                let lhs = lhs.binary_offset().unwrap();
277                let rhs = rhs.binary_offset()?;
278                let (lhs, rhs, swapped, _) = prepare_binary::<BinaryOffsetType>(lhs, rhs, true);
279                // Take slices so that vecs are not copied
280                let lhs = lhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
281                let rhs = rhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
282                let build_null_count = if swapped {
283                    s_self.null_count()
284                } else {
285                    other.null_count()
286                };
287                Ok((
288                    hash_join_tuples_inner(
289                        lhs,
290                        rhs,
291                        swapped,
292                        validate,
293                        nulls_equal,
294                        build_null_count,
295                    )?,
296                    !swapped,
297                ))
298            },
299            T::List(_) => {
300                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
301                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
302                lhs.hash_join_inner(rhs, validate, nulls_equal)
303            },
304            #[cfg(feature = "dtype-array")]
305            T::Array(_, _) => {
306                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
307                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
308                lhs.hash_join_inner(rhs, validate, nulls_equal)
309            },
310            #[cfg(feature = "dtype-struct")]
311            T::Struct(_) => {
312                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
313                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
314                lhs.hash_join_inner(rhs, validate, nulls_equal)
315            },
316            x if x.is_float() => {
317                with_match_physical_float_polars_type!(lhs.dtype(), |$T| {
318                    let lhs: &ChunkedArray<$T> = lhs.as_ref().as_ref().as_ref();
319                    let rhs: &ChunkedArray<$T> = rhs.as_ref().as_ref().as_ref();
320                    group_join_inner::<$T>(lhs, rhs, validate, nulls_equal)
321                })
322            },
323            _ => {
324                let lhs = s_self.bit_repr();
325                let rhs = other.bit_repr();
326
327                let (Some(lhs), Some(rhs)) = (lhs, rhs) else {
328                    polars_bail!(nyi = "Hash Inner Join between {lhs_dtype} and {rhs_dtype}");
329                };
330
331                use BitRepr as B;
332                match (lhs, rhs) {
333                    (B::U8(lhs), B::U8(rhs)) => {
334                        group_join_inner::<UInt8Type>(&lhs, &rhs, validate, nulls_equal)
335                    },
336                    (B::U16(lhs), B::U16(rhs)) => {
337                        group_join_inner::<UInt16Type>(&lhs, &rhs, validate, nulls_equal)
338                    },
339                    (B::U32(lhs), B::U32(rhs)) => {
340                        group_join_inner::<UInt32Type>(&lhs, &rhs, validate, nulls_equal)
341                    },
342                    (B::U64(lhs), BitRepr::U64(rhs)) => {
343                        group_join_inner::<UInt64Type>(&lhs, &rhs, validate, nulls_equal)
344                    },
345                    #[cfg(feature = "dtype-u128")]
346                    (B::U128(lhs), BitRepr::U128(rhs)) => {
347                        group_join_inner::<UInt128Type>(&lhs, &rhs, validate, nulls_equal)
348                    },
349                    _ => {
350                        polars_bail!(
351                            nyi = "Mismatch bit repr Hash Inner Join between {lhs_dtype} and {rhs_dtype}"
352                        );
353                    },
354                }
355            },
356        }
357    }
358
359    fn hash_join_outer(
360        &self,
361        other: &Series,
362        validate: JoinValidation,
363        nulls_equal: bool,
364    ) -> PolarsResult<(PrimitiveArray<IdxSize>, PrimitiveArray<IdxSize>)> {
365        let s_self = self.as_series();
366        let (lhs, rhs) = (s_self.to_physical_repr(), other.to_physical_repr());
367        validate.validate_probe(&lhs, &rhs, true, nulls_equal)?;
368
369        let lhs_dtype = lhs.dtype();
370        let rhs_dtype = rhs.dtype();
371
372        use DataType as T;
373        match lhs_dtype {
374            T::String | T::Binary => {
375                let lhs = lhs.cast(&T::Binary).unwrap();
376                let rhs = rhs.cast(&T::Binary).unwrap();
377                let lhs = lhs.binary().unwrap();
378                let rhs = rhs.binary().unwrap();
379                let (lhs, rhs, swapped, _) = prepare_binary::<BinaryType>(lhs, rhs, true);
380                // Take slices so that vecs are not copied
381                let lhs = lhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
382                let rhs = rhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
383                hash_join_tuples_outer(lhs, rhs, swapped, validate, nulls_equal)
384            },
385            T::BinaryOffset => {
386                let lhs = lhs.binary_offset().unwrap();
387                let rhs = rhs.binary_offset()?;
388                let (lhs, rhs, swapped, _) = prepare_binary::<BinaryOffsetType>(lhs, rhs, true);
389                // Take slices so that vecs are not copied
390                let lhs = lhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
391                let rhs = rhs.iter().map(|k| k.as_slice()).collect::<Vec<_>>();
392                hash_join_tuples_outer(lhs, rhs, swapped, validate, nulls_equal)
393            },
394            T::List(_) => {
395                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
396                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
397                lhs.hash_join_outer(rhs, validate, nulls_equal)
398            },
399            #[cfg(feature = "dtype-array")]
400            T::Array(_, _) => {
401                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
402                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
403                lhs.hash_join_outer(rhs, validate, nulls_equal)
404            },
405            #[cfg(feature = "dtype-struct")]
406            T::Struct(_) => {
407                let lhs = &encode_join_nested_key(lhs.into_owned(), nulls_equal)?;
408                let rhs = &encode_join_nested_key(rhs.into_owned(), nulls_equal)?;
409                lhs.hash_join_outer(rhs, validate, nulls_equal)
410            },
411            x if x.is_float() => {
412                with_match_physical_float_polars_type!(lhs.dtype(), |$T| {
413                    let lhs: &ChunkedArray<$T> = lhs.as_ref().as_ref().as_ref();
414                    let rhs: &ChunkedArray<$T> = rhs.as_ref().as_ref().as_ref();
415                    hash_join_outer(lhs, rhs, validate, nulls_equal)
416                })
417            },
418            _ => {
419                let (Some(lhs), Some(rhs)) = (s_self.bit_repr(), other.bit_repr()) else {
420                    polars_bail!(nyi = "Hash Join Outer between {lhs_dtype} and {rhs_dtype}");
421                };
422
423                use BitRepr as B;
424                match (lhs, rhs) {
425                    (B::U8(lhs), B::U8(rhs)) => hash_join_outer(&lhs, &rhs, validate, nulls_equal),
426                    (B::U16(lhs), B::U16(rhs)) => {
427                        hash_join_outer(&lhs, &rhs, validate, nulls_equal)
428                    },
429                    (B::U32(lhs), B::U32(rhs)) => {
430                        hash_join_outer(&lhs, &rhs, validate, nulls_equal)
431                    },
432                    (B::U64(lhs), B::U64(rhs)) => {
433                        hash_join_outer(&lhs, &rhs, validate, nulls_equal)
434                    },
435                    #[cfg(feature = "dtype-u128")]
436                    (B::U128(lhs), B::U128(rhs)) => {
437                        hash_join_outer(&lhs, &rhs, validate, nulls_equal)
438                    },
439                    _ => {
440                        polars_bail!(
441                            nyi = "Mismatch bit repr Hash Join Outer between {lhs_dtype} and {rhs_dtype}"
442                        );
443                    },
444                }
445            },
446        }
447    }
448}
449
450impl SeriesJoin for Series {}
451
452fn chunks_as_slices<T>(splitted: &[ChunkedArray<T>]) -> Vec<&[T::Native]>
453where
454    T: PolarsNumericType,
455{
456    splitted
457        .iter()
458        .flat_map(|ca| ca.downcast_iter().map(|arr| arr.values().as_slice()))
459        .collect()
460}
461
462fn encode_join_nested_key(s: Series, nulls_equal: bool) -> PolarsResult<Series> {
463    let by = [s.into_column()];
464    let encoded = if nulls_equal {
465        encode_rows_unordered(&by)?
466    } else {
467        encode_rows_vertical_par_unordered_broadcast_nulls(&by)?
468    };
469    Ok(encoded.into_series())
470}
471
472fn get_arrays<T: PolarsDataType>(cas: &[ChunkedArray<T>]) -> Vec<&T::Array> {
473    cas.iter().flat_map(|arr| arr.downcast_iter()).collect()
474}
475
476fn group_join_inner<T>(
477    left: &ChunkedArray<T>,
478    right: &ChunkedArray<T>,
479    validate: JoinValidation,
480    nulls_equal: bool,
481) -> PolarsResult<(InnerJoinIds, bool)>
482where
483    T: PolarsDataType,
484    for<'a> &'a T::Array: IntoIterator<Item = Option<&'a T::Physical<'a>>>,
485    for<'a> T::Physical<'a>:
486        Send + Sync + Copy + TotalHash + TotalEq + DirtyHash + IsNull + ToTotalOrd,
487    for<'a> <T::Physical<'a> as ToTotalOrd>::TotalOrdItem:
488        Send + Sync + Copy + Hash + Eq + DirtyHash + IsNull,
489{
490    let n_threads = RAYON.current_num_threads();
491    let (a, b, swapped) = det_hash_prone_order!(left, right);
492    let splitted_a = split(a, n_threads);
493    let splitted_b = split(b, n_threads);
494    let splitted_a = get_arrays(&splitted_a);
495    let splitted_b = get_arrays(&splitted_b);
496
497    match (left.null_count(), right.null_count()) {
498        (0, 0) => {
499            let first = &splitted_a[0];
500            if first.as_slice().is_some() {
501                let splitted_a = splitted_a
502                    .iter()
503                    .map(|arr| arr.as_slice().unwrap())
504                    .collect::<Vec<_>>();
505                let splitted_b = splitted_b
506                    .iter()
507                    .map(|arr| arr.as_slice().unwrap())
508                    .collect::<Vec<_>>();
509                Ok((
510                    hash_join_tuples_inner(
511                        splitted_a,
512                        splitted_b,
513                        swapped,
514                        validate,
515                        nulls_equal,
516                        0,
517                    )?,
518                    !swapped,
519                ))
520            } else {
521                Ok((
522                    hash_join_tuples_inner(
523                        splitted_a,
524                        splitted_b,
525                        swapped,
526                        validate,
527                        nulls_equal,
528                        0,
529                    )?,
530                    !swapped,
531                ))
532            }
533        },
534        _ => {
535            let build_null_count = if swapped {
536                left.null_count()
537            } else {
538                right.null_count()
539            };
540            Ok((
541                hash_join_tuples_inner(
542                    splitted_a,
543                    splitted_b,
544                    swapped,
545                    validate,
546                    nulls_equal,
547                    build_null_count,
548                )?,
549                !swapped,
550            ))
551        },
552    }
553}
554
555#[cfg(feature = "chunked_ids")]
556fn create_mappings(
557    chunks_left: &[ArrayRef],
558    chunks_right: &[ArrayRef],
559    left_len: usize,
560    right_len: usize,
561) -> (Option<Vec<ChunkId>>, Option<Vec<ChunkId>>) {
562    let mapping_left = || {
563        if chunks_left.len() > 1 {
564            Some(create_chunked_index_mapping(chunks_left, left_len))
565        } else {
566            None
567        }
568    };
569
570    let mapping_right = || {
571        if chunks_right.len() > 1 {
572            Some(create_chunked_index_mapping(chunks_right, right_len))
573        } else {
574            None
575        }
576    };
577
578    RAYON.join(mapping_left, mapping_right)
579}
580
581#[cfg(not(feature = "chunked_ids"))]
582fn create_mappings(
583    _chunks_left: &[ArrayRef],
584    _chunks_right: &[ArrayRef],
585    _left_len: usize,
586    _right_len: usize,
587) -> (Option<Vec<ChunkId>>, Option<Vec<ChunkId>>) {
588    (None, None)
589}
590
591fn num_group_join_left<T>(
592    left: &ChunkedArray<T>,
593    right: &ChunkedArray<T>,
594    validate: JoinValidation,
595    nulls_equal: bool,
596) -> PolarsResult<LeftJoinIds>
597where
598    T: PolarsNumericType,
599    T::Native: TotalHash + TotalEq + DirtyHash + IsNull + ToTotalOrd,
600    <T::Native as ToTotalOrd>::TotalOrdItem: Send + Sync + Copy + Hash + Eq + DirtyHash + IsNull,
601    T::Native: DirtyHash + Copy + ToTotalOrd,
602    <Option<T::Native> as ToTotalOrd>::TotalOrdItem: Send + Sync + DirtyHash,
603{
604    let n_threads = RAYON.current_num_threads();
605    let splitted_a = split(left, n_threads);
606    let splitted_b = split(right, n_threads);
607    match (
608        left.null_count(),
609        right.null_count(),
610        left.chunks().len(),
611        right.chunks().len(),
612    ) {
613        (0, 0, 1, 1) => {
614            let keys_a = chunks_as_slices(&splitted_a);
615            let keys_b = chunks_as_slices(&splitted_b);
616            hash_join_tuples_left(keys_a, keys_b, None, None, validate, nulls_equal, 0)
617        },
618        (0, 0, _, _) => {
619            let keys_a = chunks_as_slices(&splitted_a);
620            let keys_b = chunks_as_slices(&splitted_b);
621
622            let (mapping_left, mapping_right) =
623                create_mappings(left.chunks(), right.chunks(), left.len(), right.len());
624            hash_join_tuples_left(
625                keys_a,
626                keys_b,
627                mapping_left.as_deref(),
628                mapping_right.as_deref(),
629                validate,
630                nulls_equal,
631                0,
632            )
633        },
634        _ => {
635            let keys_a = get_arrays(&splitted_a);
636            let keys_b = get_arrays(&splitted_b);
637            let (mapping_left, mapping_right) =
638                create_mappings(left.chunks(), right.chunks(), left.len(), right.len());
639            let build_null_count = right.null_count();
640            hash_join_tuples_left(
641                keys_a,
642                keys_b,
643                mapping_left.as_deref(),
644                mapping_right.as_deref(),
645                validate,
646                nulls_equal,
647                build_null_count,
648            )
649        },
650    }
651}
652
653fn hash_join_outer<T>(
654    ca_in: &ChunkedArray<T>,
655    other: &ChunkedArray<T>,
656    validate: JoinValidation,
657    nulls_equal: bool,
658) -> PolarsResult<(PrimitiveArray<IdxSize>, PrimitiveArray<IdxSize>)>
659where
660    T: PolarsNumericType,
661    T::Native: TotalHash + TotalEq + ToTotalOrd,
662    <T::Native as ToTotalOrd>::TotalOrdItem: Send + Sync + Copy + Hash + Eq + IsNull,
663{
664    let (a, b, swapped) = det_hash_prone_order!(ca_in, other);
665
666    let n_partitions = _set_partition_size();
667    let splitted_a = split(a, n_partitions);
668    let splitted_b = split(b, n_partitions);
669
670    match (a.null_count(), b.null_count()) {
671        (0, 0) => {
672            let iters_a = splitted_a
673                .iter()
674                .flat_map(|ca| ca.downcast_iter().map(|arr| arr.values().as_slice()))
675                .collect::<Vec<_>>();
676            let iters_b = splitted_b
677                .iter()
678                .flat_map(|ca| ca.downcast_iter().map(|arr| arr.values().as_slice()))
679                .collect::<Vec<_>>();
680            hash_join_tuples_outer(iters_a, iters_b, swapped, validate, nulls_equal)
681        },
682        _ => {
683            let iters_a = splitted_a
684                .iter()
685                .flat_map(|ca| ca.downcast_iter().map(|arr| arr.iter()))
686                .collect::<Vec<_>>();
687            let iters_b = splitted_b
688                .iter()
689                .flat_map(|ca| ca.downcast_iter().map(|arr| arr.iter()))
690                .collect::<Vec<_>>();
691            hash_join_tuples_outer(iters_a, iters_b, swapped, validate, nulls_equal)
692        },
693    }
694}
695
696pub(crate) fn prepare_binary<'a, T>(
697    ca: &'a ChunkedArray<T>,
698    other: &'a ChunkedArray<T>,
699    // In inner join and outer join, the shortest relation will be used to create a hash table.
700    // In left join, always use the right side to create.
701    build_shortest_table: bool,
702) -> (
703    Vec<Vec<BytesHash<'a>>>,
704    Vec<Vec<BytesHash<'a>>>,
705    bool,
706    PlRandomState,
707)
708where
709    T: PolarsDataType,
710    for<'b> <T::Array as StaticArray>::ValueT<'b>: AsRef<[u8]>,
711{
712    let (a, b, swapped) = if build_shortest_table {
713        det_hash_prone_order!(ca, other)
714    } else {
715        (ca, other, false)
716    };
717    let hb = PlRandomState::default();
718    let bh_a = a.to_bytes_hashes(true, hb.clone());
719    let bh_b = b.to_bytes_hashes(true, hb.clone());
720
721    (bh_a, bh_b, swapped, hb)
722}
723
724#[cfg(feature = "semi_anti_join")]
725fn num_group_join_anti_semi<T>(
726    left: &ChunkedArray<T>,
727    right: &ChunkedArray<T>,
728    anti: bool,
729    nulls_equal: bool,
730) -> Vec<IdxSize>
731where
732    T: PolarsNumericType,
733    T::Native: TotalHash + TotalEq + DirtyHash + ToTotalOrd,
734    <T::Native as ToTotalOrd>::TotalOrdItem: Send + Sync + Copy + Hash + Eq + DirtyHash + IsNull,
735    <Option<T::Native> as ToTotalOrd>::TotalOrdItem: Send + Sync + DirtyHash + IsNull,
736{
737    let n_threads = RAYON.current_num_threads();
738    let splitted_a = split(left, n_threads);
739    let splitted_b = split(right, n_threads);
740    match (
741        left.null_count(),
742        right.null_count(),
743        left.chunks().len(),
744        right.chunks().len(),
745    ) {
746        (0, 0, 1, 1) => {
747            let keys_a = chunks_as_slices(&splitted_a);
748            let keys_b = chunks_as_slices(&splitted_b);
749            if anti {
750                hash_join_tuples_left_anti(keys_a, keys_b, nulls_equal)
751            } else {
752                hash_join_tuples_left_semi(keys_a, keys_b, nulls_equal)
753            }
754        },
755        (0, 0, _, _) => {
756            let keys_a = chunks_as_slices(&splitted_a);
757            let keys_b = chunks_as_slices(&splitted_b);
758            if anti {
759                hash_join_tuples_left_anti(keys_a, keys_b, nulls_equal)
760            } else {
761                hash_join_tuples_left_semi(keys_a, keys_b, nulls_equal)
762            }
763        },
764        _ => {
765            let keys_a = get_arrays(&splitted_a);
766            let keys_b = get_arrays(&splitted_b);
767            if anti {
768                hash_join_tuples_left_anti(keys_a, keys_b, nulls_equal)
769            } else {
770                hash_join_tuples_left_semi(keys_a, keys_b, nulls_equal)
771            }
772        },
773    }
774}