1use std::{fmt::Display, hash::Hash, sync::Arc};
21
22use arrow::{
23 array::{ArrayRef, UInt64Array},
24 datatypes::{DataType, Schema},
25 record_batch::RecordBatch,
26};
27use datafusion_common::Result;
28use datafusion_common::hash_utils::RandomState;
29use datafusion_common::hash_utils::{create_hashes, with_hashes};
30#[cfg(feature = "proto")]
31use datafusion_common::internal_err;
32use datafusion_expr::ColumnarValue;
33use datafusion_physical_expr_common::physical_expr::{
34 DynHash, PhysicalExpr, PhysicalExprRef,
35};
36
37use crate::joins::Map;
38
39#[derive(Clone, Debug)]
44pub struct SeededRandomState {
45 random_state: RandomState,
46 seed: u64,
47}
48
49impl SeededRandomState {
50 pub const fn with_seed(k: u64) -> Self {
52 Self {
53 random_state: RandomState::with_seed(k),
54 seed: k,
55 }
56 }
57
58 pub fn random_state(&self) -> &RandomState {
60 &self.random_state
61 }
62
63 pub fn seed(&self) -> u64 {
65 self.seed
66 }
67}
68
69pub struct HashExpr {
78 on_columns: Vec<PhysicalExprRef>,
80 random_state: SeededRandomState,
82 description: String,
84}
85
86impl HashExpr {
87 pub fn new(
94 on_columns: Vec<PhysicalExprRef>,
95 random_state: SeededRandomState,
96 description: String,
97 ) -> Self {
98 Self {
99 on_columns,
100 random_state,
101 description,
102 }
103 }
104
105 pub fn on_columns(&self) -> &[PhysicalExprRef] {
107 &self.on_columns
108 }
109
110 pub fn seed(&self) -> u64 {
112 self.random_state.seed()
113 }
114
115 pub fn description(&self) -> &str {
117 &self.description
118 }
119}
120
121impl std::fmt::Debug for HashExpr {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 let cols = self
124 .on_columns
125 .iter()
126 .map(|e| e.to_string())
127 .collect::<Vec<_>>()
128 .join(", ");
129 let seed = self.seed();
130 write!(f, "{}({cols}, [{seed}])", self.description)
131 }
132}
133
134impl Hash for HashExpr {
135 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
136 self.on_columns.dyn_hash(state);
137 self.description.hash(state);
138 self.seed().hash(state);
139 }
140}
141
142impl PartialEq for HashExpr {
143 fn eq(&self, other: &Self) -> bool {
144 self.on_columns == other.on_columns
145 && self.description == other.description
146 && self.seed() == other.seed()
147 }
148}
149
150impl Eq for HashExpr {}
151
152impl Display for HashExpr {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 write!(f, "{}", self.description)
155 }
156}
157
158impl PhysicalExpr for HashExpr {
159 fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
160 self.on_columns.iter().collect()
161 }
162
163 fn with_new_children(
164 self: Arc<Self>,
165 children: Vec<Arc<dyn PhysicalExpr>>,
166 ) -> Result<Arc<dyn PhysicalExpr>> {
167 Ok(Arc::new(HashExpr::new(
168 children,
169 self.random_state.clone(),
170 self.description.clone(),
171 )))
172 }
173
174 fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
175 Ok(DataType::UInt64)
176 }
177
178 fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
179 Ok(false)
180 }
181
182 fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
183 let num_rows = batch.num_rows();
184
185 let keys_values = evaluate_columns(&self.on_columns, batch)?;
187
188 let mut hashes_buffer = vec![0; num_rows];
190 create_hashes(
191 &keys_values,
192 self.random_state.random_state(),
193 &mut hashes_buffer,
194 )?;
195
196 Ok(ColumnarValue::Array(Arc::new(UInt64Array::from(
197 hashes_buffer,
198 ))))
199 }
200
201 fn fmt_sql(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 write!(f, "{}", self.description)
203 }
204
205 #[cfg(feature = "proto")]
206 fn try_to_proto(
207 &self,
208 ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
209 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
210 use datafusion_proto_models::protobuf;
211 let on_columns = ctx.encode_children_expressions(&self.on_columns)?;
212 Ok(Some(protobuf::PhysicalExprNode {
213 expr_id: None,
214 expr_type: Some(protobuf::physical_expr_node::ExprType::HashExpr(
215 protobuf::PhysicalHashExprNode {
216 on_columns,
217 seed0: self.seed(),
218 description: self.description.clone(),
219 },
220 )),
221 }))
222 }
223}
224
225#[cfg(feature = "proto")]
226impl HashExpr {
227 pub fn try_from_proto(
238 node: &datafusion_proto_models::protobuf::PhysicalExprNode,
239 ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
240 ) -> Result<Arc<dyn PhysicalExpr>> {
241 use datafusion_proto_models::protobuf;
242 let hash_expr = match &node.expr_type {
243 Some(protobuf::physical_expr_node::ExprType::HashExpr(h)) => h,
244 _ => return internal_err!("PhysicalExprNode is not a HashExpr"),
245 };
246 let on_columns = ctx.decode_children_expressions(&hash_expr.on_columns)?;
247 Ok(Arc::new(HashExpr::new(
248 on_columns,
249 SeededRandomState::with_seed(hash_expr.seed0),
250 hash_expr.description.clone(),
251 )))
252 }
253}
254
255pub struct HashTableLookupExpr {
260 on_columns: Vec<PhysicalExprRef>,
262 random_state: SeededRandomState,
264 map: Arc<Map>,
266 description: String,
268}
269impl HashTableLookupExpr {
270 pub fn new(
281 on_columns: Vec<PhysicalExprRef>,
282 random_state: SeededRandomState,
283 map: Arc<Map>,
284 description: String,
285 ) -> Self {
286 Self {
287 on_columns,
288 random_state,
289 map,
290 description,
291 }
292 }
293}
294impl std::fmt::Debug for HashTableLookupExpr {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 let cols = self
297 .on_columns
298 .iter()
299 .map(|e| e.to_string())
300 .collect::<Vec<_>>()
301 .join(", ");
302 let seed = self.random_state.seed();
303 write!(f, "{}({cols}, [{seed}])", self.description)
304 }
305}
306
307impl Hash for HashTableLookupExpr {
308 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
309 self.on_columns.dyn_hash(state);
310 self.description.hash(state);
311 self.random_state.seed().hash(state);
312 Arc::as_ptr(&self.map).hash(state);
320 }
321}
322
323impl PartialEq for HashTableLookupExpr {
324 fn eq(&self, other: &Self) -> bool {
325 self.on_columns == other.on_columns
333 && self.description == other.description
334 && self.random_state.seed() == other.random_state.seed()
335 && Arc::ptr_eq(&self.map, &other.map)
336 }
337}
338
339impl Eq for HashTableLookupExpr {}
340
341impl Display for HashTableLookupExpr {
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 write!(f, "{}", self.description)
344 }
345}
346
347impl PhysicalExpr for HashTableLookupExpr {
348 fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
349 self.on_columns.iter().collect()
350 }
351
352 fn with_new_children(
353 self: Arc<Self>,
354 children: Vec<Arc<dyn PhysicalExpr>>,
355 ) -> Result<Arc<dyn PhysicalExpr>> {
356 Ok(Arc::new(HashTableLookupExpr::new(
357 children,
358 self.random_state.clone(),
359 Arc::clone(&self.map),
360 self.description.clone(),
361 )))
362 }
363
364 fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
365 Ok(DataType::Boolean)
366 }
367
368 fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
369 Ok(false)
370 }
371
372 fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
373 let join_keys = evaluate_columns(&self.on_columns, batch)?;
375
376 match self.map.as_ref() {
377 Map::HashMap(map) => {
378 with_hashes(&join_keys, self.random_state.random_state(), |hashes| {
379 let array = map.contain_hashes(hashes);
380 Ok(ColumnarValue::Array(Arc::new(array)))
381 })
382 }
383 Map::ArrayMap(map) => {
384 let array = map.contain_keys(&join_keys)?;
385 Ok(ColumnarValue::Array(Arc::new(array)))
386 }
387 }
388 }
389 #[cfg(feature = "proto")]
390 fn try_to_proto(
391 &self,
392 _ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
393 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
394 use datafusion_proto_models::protobuf;
395 use datafusion_proto_models::protobuf::physical_expr_node::ExprType;
396
397 let value = datafusion_proto_common::ScalarValue {
412 value: Some(datafusion_proto_common::scalar_value::Value::BoolValue(
413 true,
414 )),
415 };
416 Ok(Some(protobuf::PhysicalExprNode {
417 expr_id: None,
418 expr_type: Some(ExprType::Literal(value)),
419 }))
420 }
421 fn fmt_sql(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 write!(f, "{}", self.description)
423 }
424}
425
426fn evaluate_columns(
427 columns: &[PhysicalExprRef],
428 batch: &RecordBatch,
429) -> Result<Vec<ArrayRef>> {
430 let num_rows = batch.num_rows();
431 columns
432 .iter()
433 .map(|c| c.evaluate(batch)?.into_array(num_rows))
434 .collect()
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440 use crate::joins::join_hash_map::JoinHashMapU32;
441 use datafusion_physical_expr::expressions::Column;
442 use std::collections::hash_map::DefaultHasher;
443 use std::hash::Hasher;
444
445 fn compute_hash<T: Hash>(value: &T) -> u64 {
446 let mut hasher = DefaultHasher::new();
447 value.hash(&mut hasher);
448 hasher.finish()
449 }
450
451 #[test]
452 fn test_hash_expr_eq_same() {
453 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
454 let col_b: PhysicalExprRef = Arc::new(Column::new("b", 1));
455
456 let expr1 = HashExpr::new(
457 vec![Arc::clone(&col_a), Arc::clone(&col_b)],
458 SeededRandomState::with_seed(1),
459 "test_hash".to_string(),
460 );
461
462 let expr2 = HashExpr::new(
463 vec![Arc::clone(&col_a), Arc::clone(&col_b)],
464 SeededRandomState::with_seed(1),
465 "test_hash".to_string(),
466 );
467
468 assert_eq!(expr1, expr2);
469 }
470
471 #[test]
472 fn test_hash_expr_eq_different_columns() {
473 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
474 let col_b: PhysicalExprRef = Arc::new(Column::new("b", 1));
475 let col_c: PhysicalExprRef = Arc::new(Column::new("c", 2));
476
477 let expr1 = HashExpr::new(
478 vec![Arc::clone(&col_a), Arc::clone(&col_b)],
479 SeededRandomState::with_seed(1),
480 "test_hash".to_string(),
481 );
482
483 let expr2 = HashExpr::new(
484 vec![Arc::clone(&col_a), Arc::clone(&col_c)],
485 SeededRandomState::with_seed(1),
486 "test_hash".to_string(),
487 );
488
489 assert_ne!(expr1, expr2);
490 }
491
492 #[test]
493 fn test_hash_expr_eq_different_description() {
494 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
495
496 let expr1 = HashExpr::new(
497 vec![Arc::clone(&col_a)],
498 SeededRandomState::with_seed(1),
499 "hash_one".to_string(),
500 );
501
502 let expr2 = HashExpr::new(
503 vec![Arc::clone(&col_a)],
504 SeededRandomState::with_seed(1),
505 "hash_two".to_string(),
506 );
507
508 assert_ne!(expr1, expr2);
509 }
510
511 #[test]
512 fn test_hash_expr_eq_different_seeds() {
513 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
514
515 let expr1 = HashExpr::new(
516 vec![Arc::clone(&col_a)],
517 SeededRandomState::with_seed(1),
518 "test_hash".to_string(),
519 );
520
521 let expr2 = HashExpr::new(
522 vec![Arc::clone(&col_a)],
523 SeededRandomState::with_seed(5),
524 "test_hash".to_string(),
525 );
526
527 assert_ne!(expr1, expr2);
528 }
529
530 #[test]
531 fn test_hash_expr_hash_consistency() {
532 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
533 let col_b: PhysicalExprRef = Arc::new(Column::new("b", 1));
534
535 let expr1 = HashExpr::new(
536 vec![Arc::clone(&col_a), Arc::clone(&col_b)],
537 SeededRandomState::with_seed(1),
538 "test_hash".to_string(),
539 );
540
541 let expr2 = HashExpr::new(
542 vec![Arc::clone(&col_a), Arc::clone(&col_b)],
543 SeededRandomState::with_seed(1),
544 "test_hash".to_string(),
545 );
546
547 assert_eq!(expr1, expr2);
549 assert_eq!(compute_hash(&expr1), compute_hash(&expr2));
550 }
551
552 #[cfg(feature = "proto")]
553 mod proto_tests {
554 use super::*;
555 use arrow::datatypes::{DataType, Field};
556 use datafusion_common::internal_datafusion_err;
557 use datafusion_physical_expr_common::physical_expr::proto_decode::{
558 PhysicalExprDecode, PhysicalExprDecodeCtx,
559 };
560 use datafusion_physical_expr_common::physical_expr::proto_encode::{
561 PhysicalExprEncode, PhysicalExprEncodeCtx,
562 };
563 use datafusion_proto_models::protobuf;
564
565 struct TestEncoder;
566
567 impl PhysicalExprEncode for TestEncoder {
568 fn encode(
569 &self,
570 expr: &Arc<dyn PhysicalExpr>,
571 ) -> Result<protobuf::PhysicalExprNode> {
572 let ctx = PhysicalExprEncodeCtx::new(self);
573 expr.try_to_proto(&ctx)?.ok_or_else(|| {
574 internal_datafusion_err!("test encoder cannot encode {expr:?}")
575 })
576 }
577 }
578
579 struct TestDecoder;
580
581 impl PhysicalExprDecode for TestDecoder {
582 fn decode(
583 &self,
584 node: &protobuf::PhysicalExprNode,
585 schema: &Schema,
586 ) -> Result<Arc<dyn PhysicalExpr>> {
587 let ctx = PhysicalExprDecodeCtx::new(schema, self);
588 match &node.expr_type {
589 Some(protobuf::physical_expr_node::ExprType::Column(_)) => {
590 Column::try_from_proto(node, &ctx)
591 }
592 _ => internal_err!("test decoder cannot decode {node:?}"),
593 }
594 }
595 }
596
597 fn test_decode_ctx<'a>(
598 schema: &'a Schema,
599 decoder: &'a TestDecoder,
600 ) -> PhysicalExprDecodeCtx<'a> {
601 PhysicalExprDecodeCtx::new(schema, decoder)
602 }
603
604 #[test]
605 fn hash_expr_try_to_proto() {
606 let expr = HashExpr::new(
607 vec![Arc::new(Column::new("a", 0)), Arc::new(Column::new("b", 1))],
608 SeededRandomState::with_seed(42),
609 "hash_join".to_string(),
610 );
611 let encoder = TestEncoder;
612 let ctx = PhysicalExprEncodeCtx::new(&encoder);
613
614 let proto = expr.try_to_proto(&ctx).unwrap().unwrap();
615
616 assert_eq!(proto.expr_id, None);
617 let hash_expr = match proto.expr_type.unwrap() {
618 protobuf::physical_expr_node::ExprType::HashExpr(hash_expr) => hash_expr,
619 other => panic!("expected HashExpr, got {other:?}"),
620 };
621 assert_eq!(hash_expr.seed0, 42);
622 assert_eq!(hash_expr.description, "hash_join");
623 assert_eq!(hash_expr.on_columns.len(), 2);
624 assert!(
625 hash_expr
626 .on_columns
627 .iter()
628 .all(|expr| expr.expr_id.is_none())
629 );
630 }
631
632 #[test]
633 fn hash_expr_try_from_proto() {
634 let schema = Schema::new(vec![
635 Field::new("a", DataType::Int32, false),
636 Field::new("b", DataType::Utf8, true),
637 ]);
638 let decoder = TestDecoder;
639 let ctx = test_decode_ctx(&schema, &decoder);
640 let proto = protobuf::PhysicalExprNode {
641 expr_id: None,
642 expr_type: Some(protobuf::physical_expr_node::ExprType::HashExpr(
643 protobuf::PhysicalHashExprNode {
644 on_columns: vec![
645 protobuf::PhysicalExprNode {
646 expr_id: None,
647 expr_type: Some(
648 protobuf::physical_expr_node::ExprType::Column(
649 protobuf::PhysicalColumn {
650 name: "a".to_string(),
651 index: 0,
652 },
653 ),
654 ),
655 },
656 protobuf::PhysicalExprNode {
657 expr_id: None,
658 expr_type: Some(
659 protobuf::physical_expr_node::ExprType::Column(
660 protobuf::PhysicalColumn {
661 name: "b".to_string(),
662 index: 1,
663 },
664 ),
665 ),
666 },
667 ],
668 seed0: 42,
669 description: "hash_join".to_string(),
670 },
671 )),
672 };
673
674 let expr = HashExpr::try_from_proto(&proto, &ctx).unwrap();
675 let expr = expr.downcast_ref::<HashExpr>().unwrap();
676
677 assert_eq!(expr.seed(), 42);
678 assert_eq!(expr.description(), "hash_join");
679 assert_eq!(expr.on_columns().len(), 2);
680 assert_eq!(
681 expr.on_columns()[0]
682 .downcast_ref::<Column>()
683 .map(|col| (col.name(), col.index())),
684 Some(("a", 0))
685 );
686 assert_eq!(
687 expr.on_columns()[1]
688 .downcast_ref::<Column>()
689 .map(|col| (col.name(), col.index())),
690 Some(("b", 1))
691 );
692 }
693
694 #[test]
695 fn hash_expr_try_from_proto_rejects_wrong_node_type() {
696 let schema = Schema::empty();
697 let decoder = TestDecoder;
698 let ctx = test_decode_ctx(&schema, &decoder);
699 let proto = protobuf::PhysicalExprNode {
700 expr_id: None,
701 expr_type: Some(protobuf::physical_expr_node::ExprType::Column(
702 protobuf::PhysicalColumn {
703 name: "a".to_string(),
704 index: 0,
705 },
706 )),
707 };
708
709 let err = HashExpr::try_from_proto(&proto, &ctx).unwrap_err();
710 assert!(
711 err.to_string()
712 .contains("PhysicalExprNode is not a HashExpr"),
713 "{err}"
714 );
715 }
716 }
717
718 #[test]
719 fn test_hash_table_lookup_expr_eq_same() {
720 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
721 let hash_map =
722 Arc::new(Map::HashMap(Box::new(JoinHashMapU32::with_capacity(10))));
723
724 let expr1 = HashTableLookupExpr::new(
725 vec![Arc::clone(&col_a)],
726 SeededRandomState::with_seed(1),
727 Arc::clone(&hash_map),
728 "lookup".to_string(),
729 );
730
731 let expr2 = HashTableLookupExpr::new(
732 vec![Arc::clone(&col_a)],
733 SeededRandomState::with_seed(1),
734 Arc::clone(&hash_map),
735 "lookup".to_string(),
736 );
737
738 assert_eq!(expr1, expr2);
739 }
740
741 #[test]
742 fn test_hash_table_lookup_expr_eq_different_columns() {
743 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
744 let col_b: PhysicalExprRef = Arc::new(Column::new("b", 1));
745
746 let hash_map =
747 Arc::new(Map::HashMap(Box::new(JoinHashMapU32::with_capacity(10))));
748
749 let expr1 = HashTableLookupExpr::new(
750 vec![Arc::clone(&col_a)],
751 SeededRandomState::with_seed(1),
752 Arc::clone(&hash_map),
753 "lookup".to_string(),
754 );
755
756 let expr2 = HashTableLookupExpr::new(
757 vec![Arc::clone(&col_b)],
758 SeededRandomState::with_seed(1),
759 Arc::clone(&hash_map),
760 "lookup".to_string(),
761 );
762
763 assert_ne!(expr1, expr2);
764 }
765
766 #[test]
767 fn test_hash_table_lookup_expr_eq_different_description() {
768 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
769 let hash_map =
770 Arc::new(Map::HashMap(Box::new(JoinHashMapU32::with_capacity(10))));
771
772 let expr1 = HashTableLookupExpr::new(
773 vec![Arc::clone(&col_a)],
774 SeededRandomState::with_seed(1),
775 Arc::clone(&hash_map),
776 "lookup_one".to_string(),
777 );
778
779 let expr2 = HashTableLookupExpr::new(
780 vec![Arc::clone(&col_a)],
781 SeededRandomState::with_seed(1),
782 Arc::clone(&hash_map),
783 "lookup_two".to_string(),
784 );
785
786 assert_ne!(expr1, expr2);
787 }
788
789 #[test]
790 fn test_hash_table_lookup_expr_eq_different_hash_map() {
791 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
792
793 let hash_map1 =
795 Arc::new(Map::HashMap(Box::new(JoinHashMapU32::with_capacity(10))));
796 let hash_map2 =
797 Arc::new(Map::HashMap(Box::new(JoinHashMapU32::with_capacity(10))));
798 let expr1 = HashTableLookupExpr::new(
799 vec![Arc::clone(&col_a)],
800 SeededRandomState::with_seed(1),
801 hash_map1,
802 "lookup".to_string(),
803 );
804
805 let expr2 = HashTableLookupExpr::new(
806 vec![Arc::clone(&col_a)],
807 SeededRandomState::with_seed(1),
808 hash_map2,
809 "lookup".to_string(),
810 );
811
812 assert_ne!(expr1, expr2);
814 }
815
816 #[test]
817 fn test_hash_table_lookup_expr_hash_consistency() {
818 let col_a: PhysicalExprRef = Arc::new(Column::new("a", 0));
819 let hash_map =
820 Arc::new(Map::HashMap(Box::new(JoinHashMapU32::with_capacity(10))));
821
822 let expr1 = HashTableLookupExpr::new(
823 vec![Arc::clone(&col_a)],
824 SeededRandomState::with_seed(1),
825 Arc::clone(&hash_map),
826 "lookup".to_string(),
827 );
828
829 let expr2 = HashTableLookupExpr::new(
830 vec![Arc::clone(&col_a)],
831 SeededRandomState::with_seed(1),
832 Arc::clone(&hash_map),
833 "lookup".to_string(),
834 );
835
836 assert_eq!(expr1, expr2);
838 assert_eq!(compute_hash(&expr1), compute_hash(&expr2));
839 }
840}