gqlite 1.8.0 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +8 -8
  3. data/ext/db-index/Cargo.toml +2 -2
  4. data/ext/gqlitedb/Cargo.toml +0 -2
  5. data/ext/gqlitedb/src/aggregators.rs +28 -0
  6. data/ext/gqlitedb/src/compiler/expression_analyser.rs +33 -0
  7. data/ext/gqlitedb/src/compiler/variables_manager.rs +111 -11
  8. data/ext/gqlitedb/src/compiler.rs +506 -48
  9. data/ext/gqlitedb/src/connection.rs +14 -2
  10. data/ext/gqlitedb/src/consts.rs +1 -1
  11. data/ext/gqlitedb/src/error.rs +31 -0
  12. data/ext/gqlitedb/src/interpreter/evaluators.rs +153 -331
  13. data/ext/gqlitedb/src/interpreter/instructions.rs +77 -0
  14. data/ext/gqlitedb/src/interpreter/mod.rs +1 -0
  15. data/ext/gqlitedb/src/interpreter/path/bfs.rs +494 -0
  16. data/ext/gqlitedb/src/interpreter/path/dfs.rs +267 -0
  17. data/ext/gqlitedb/src/interpreter/path/group.rs +418 -0
  18. data/ext/gqlitedb/src/interpreter/path.rs +542 -0
  19. data/ext/gqlitedb/src/planner.rs +231 -6
  20. data/ext/gqlitedb/src/query_result.rs +19 -4
  21. data/ext/gqlitedb/src/store/redb.rs +1 -1
  22. data/ext/gqlitedb/src/tests/compiler.rs +2454 -1
  23. data/ext/gqlitedb/src/tests/connection/postgres.rs +22 -0
  24. data/ext/gqlitedb/src/tests/connection/redb.rs +24 -0
  25. data/ext/gqlitedb/src/tests/connection/sqlite.rs +16 -0
  26. data/ext/gqlitedb/src/tests/connection.rs +101 -0
  27. data/ext/gqlitedb/src/tests/evaluators.rs +2206 -1
  28. data/ext/gqlitedb/src/tests/parser.rs +1225 -0
  29. data/ext/gqlitedb/src/tests/planner.rs +433 -2
  30. data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +5 -5
  31. data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +3 -1
  32. data/ext/gqlitedb/src/tests/templates/ast.rs +294 -1
  33. data/ext/gqlitedb/src/tests/templates/programs.rs +318 -0
  34. data/ext/gqliterb/Cargo.toml +0 -1
  35. data/ext/gqlparser/src/oc/ast.rs +72 -3
  36. data/ext/gqlparser/src/oc/error.rs +18 -0
  37. data/ext/gqlparser/src/oc/lexer.rs +157 -2
  38. data/ext/gqlparser/src/oc/parser/tests.rs +735 -29
  39. data/ext/gqlparser/src/oc/parser.rs +1312 -182
  40. data/ext/gqlparser/src/oc/string.rs +172 -0
  41. data/ext/gqlparser/src/oc.rs +1 -0
  42. data/ext/graphcore/src/value/value_map.rs +2 -2
  43. data/ext/graphcore/src/value.rs +13 -1
  44. metadata +6 -2
  45. data/ext/gqlitedb/src/parser/gql.pest +0 -198
@@ -53,6 +53,8 @@ fn test_plan_match_creates_scan_and_filter()
53
53
  variable: Some(node_identifier.clone()),
54
54
  labels: LabelExpression::String("Person".into()),
55
55
  properties: None,
56
+
57
+ predicate: None,
56
58
  })],
57
59
  where_expression: Some(
58
60
  Variable {
@@ -74,7 +76,7 @@ fn test_plan_match_creates_scan_and_filter()
74
76
  LogicalPlan::NodeScan { patterns, optional: true }
75
77
  if matches!(
76
78
  &patterns[0],
77
- Pattern::Node(NodePattern {
79
+ planner::PlannedPattern::Node(NodePattern {
78
80
  labels: LabelExpression::String(label),
79
81
  ..
80
82
  }) if label == "Person"
@@ -99,6 +101,7 @@ fn test_plan_return_creates_projection_modifier_chain()
99
101
  .into(),
100
102
  }],
101
103
  modifiers: Modifiers {
104
+ distinct: false,
102
105
  skip: Some(Value { value: 2.into() }.into()),
103
106
  limit: Some(Value { value: 4.into() }.into()),
104
107
  order_by: Some(OrderBy {
@@ -220,6 +223,8 @@ fn test_plan_maps_direct_statements()
220
223
  variable: Some(node_identifier.clone()),
221
224
  labels: LabelExpression::None,
222
225
  properties: None,
226
+
227
+ predicate: None,
223
228
  })],
224
229
  }
225
230
  .into(),
@@ -339,6 +344,8 @@ fn test_plan_match_with_search_emits_vector_search()
339
344
  variable: Some(node_identifier.clone()),
340
345
  labels: LabelExpression::String("Doc".into()),
341
346
  properties: None,
347
+
348
+ predicate: None,
342
349
  })],
343
350
  where_expression: None,
344
351
  optional: false,
@@ -390,6 +397,8 @@ fn test_plan_match_with_search_and_score()
390
397
  variable: Some(node_identifier.clone()),
391
398
  labels: LabelExpression::String("Doc".into()),
392
399
  properties: None,
400
+
401
+ predicate: None,
393
402
  })],
394
403
  where_expression: None,
395
404
  optional: false,
@@ -439,6 +448,8 @@ fn test_plan_match_without_search_still_emits_node_scan()
439
448
  variable: Some(node_identifier.clone()),
440
449
  labels: LabelExpression::String("Person".into()),
441
450
  properties: None,
451
+
452
+ predicate: None,
442
453
  })],
443
454
  where_expression: None,
444
455
  optional: false,
@@ -456,7 +467,7 @@ fn test_plan_match_without_search_still_emits_node_scan()
456
467
  }
457
468
  if matches!(
458
469
  &patterns[0],
459
- Pattern::Node(NodePattern {
470
+ planner::PlannedPattern::Node(NodePattern {
460
471
  labels: LabelExpression::String(label),
461
472
  ..
462
473
  }) if label == "Person"
@@ -476,6 +487,8 @@ fn test_plan_match_with_search_and_filter()
476
487
  variable: Some(node_identifier.clone()),
477
488
  labels: LabelExpression::String("Doc".into()),
478
489
  properties: None,
490
+
491
+ predicate: None,
479
492
  })],
480
493
  where_expression: Some(Value { value: true.into() }.into()),
481
494
  optional: false,
@@ -509,3 +522,421 @@ fn test_plan_match_with_search_and_filter()
509
522
  && *k == 3
510
523
  ));
511
524
  }
525
+
526
+ #[test]
527
+ fn test_choose_path_strategy_all_to_dfs()
528
+ {
529
+ let selector = PathSelector::All;
530
+ assert_eq!(
531
+ planner::choose_path_strategy(&selector),
532
+ planner::PathStrategy::Dfs
533
+ );
534
+ }
535
+
536
+ #[test]
537
+ fn test_choose_path_strategy_any_to_bfs()
538
+ {
539
+ let selector = PathSelector::Any;
540
+ assert_eq!(
541
+ planner::choose_path_strategy(&selector),
542
+ planner::PathStrategy::Bfs {
543
+ stop_condition: planner::StopCondition::Shortest(Value { value: 1i64.into() }.into()),
544
+ }
545
+ );
546
+ }
547
+
548
+ #[test]
549
+ fn test_choose_path_strategy_all_shortest_to_bfs()
550
+ {
551
+ let selector = PathSelector::AllShortest;
552
+ assert_eq!(
553
+ planner::choose_path_strategy(&selector),
554
+ planner::PathStrategy::Bfs {
555
+ stop_condition: planner::StopCondition::ShortestGroups(Value { value: 1i64.into() }.into()),
556
+ }
557
+ );
558
+ }
559
+
560
+ #[test]
561
+ fn test_choose_path_strategy_shortest_to_bfs()
562
+ {
563
+ let selector = PathSelector::Shortest(Value { value: 10.into() }.into());
564
+ assert_eq!(
565
+ planner::choose_path_strategy(&selector),
566
+ planner::PathStrategy::Bfs {
567
+ stop_condition: planner::StopCondition::Shortest(Value { value: 10.into() }.into()),
568
+ }
569
+ );
570
+ }
571
+
572
+ #[test]
573
+ fn test_choose_path_strategy_shortest_groups_to_bfs()
574
+ {
575
+ let selector = PathSelector::ShortestGroups(Value { value: 5.into() }.into());
576
+ assert_eq!(
577
+ planner::choose_path_strategy(&selector),
578
+ planner::PathStrategy::Bfs {
579
+ stop_condition: planner::StopCondition::ShortestGroups(Value { value: 5.into() }.into()),
580
+ }
581
+ );
582
+ }
583
+
584
+ #[test]
585
+ fn test_plan_pattern_node_passes_through()
586
+ {
587
+ let identifiers = VariableIdentifiers::default();
588
+ let n_identifier = identifiers.create_variable_from_name("n");
589
+
590
+ let node_pattern = Pattern::Node(NodePattern {
591
+ variable: Some(n_identifier.clone()),
592
+ labels: LabelExpression::String("Person".into()),
593
+ properties: None,
594
+ predicate: None,
595
+ });
596
+
597
+ let planned = planner::plan_pattern(node_pattern);
598
+ assert!(matches!(planned, planner::PlannedPattern::Node(_)));
599
+ }
600
+
601
+ #[test]
602
+ fn test_plan_pattern_edge_passes_through()
603
+ {
604
+ let node_a = NodePattern {
605
+ variable: None,
606
+ labels: LabelExpression::None,
607
+ properties: None,
608
+ predicate: None,
609
+ };
610
+ let node_b = NodePattern {
611
+ variable: None,
612
+ labels: LabelExpression::None,
613
+ properties: None,
614
+ predicate: None,
615
+ };
616
+
617
+ let edge_pattern = Pattern::Edge(EdgePattern {
618
+ variable: None,
619
+ source: node_a,
620
+ destination: node_b,
621
+ directivity: graphcore::EdgeDirectivity::Directed,
622
+ labels: LabelExpression::String("R".into()),
623
+ properties: None,
624
+ predicate: None,
625
+ });
626
+
627
+ let planned = planner::plan_pattern(edge_pattern);
628
+ assert!(matches!(planned, planner::PlannedPattern::Edge(_)));
629
+ }
630
+
631
+ #[test]
632
+ fn test_plan_pattern_quantified_path_uses_dfs_for_all()
633
+ {
634
+ let identifiers = VariableIdentifiers::default();
635
+ let node_a = NodePattern {
636
+ variable: Some(identifiers.create_variable_from_name("a")),
637
+ labels: LabelExpression::None,
638
+ properties: None,
639
+ predicate: None,
640
+ };
641
+ let node_b = NodePattern {
642
+ variable: None,
643
+ labels: LabelExpression::None,
644
+ properties: None,
645
+ predicate: None,
646
+ };
647
+
648
+ let qp_source = node_a.clone();
649
+ let qp_destination = node_b.clone();
650
+ let edge_pattern = EdgePattern {
651
+ variable: None,
652
+ source: node_a,
653
+ destination: node_b,
654
+ directivity: graphcore::EdgeDirectivity::Directed,
655
+ labels: LabelExpression::String("R".into()),
656
+ properties: None,
657
+ predicate: None,
658
+ };
659
+
660
+ let quantified_pattern = Pattern::QuantifiedPath(QuantifiedPathPattern {
661
+ variable: None,
662
+ source: qp_source,
663
+ group: vec![Pattern::Edge(edge_pattern)],
664
+ group_where: None,
665
+ path_where: None,
666
+ quantifier: Some(1..3),
667
+ destination: qp_destination,
668
+ selector: PathSelector::All,
669
+ });
670
+
671
+ let planned = planner::plan_pattern(quantified_pattern);
672
+ assert!(matches!(
673
+ planned,
674
+ planner::PlannedPattern::QuantifiedPath { strategy, .. }
675
+ if strategy == planner::PathStrategy::Dfs
676
+ ));
677
+ }
678
+
679
+ #[test]
680
+ fn test_plan_pattern_quantified_path_uses_bfs_for_shortest()
681
+ {
682
+ let identifiers = VariableIdentifiers::default();
683
+ let node_a = NodePattern {
684
+ variable: Some(identifiers.create_variable_from_name("a")),
685
+ labels: LabelExpression::None,
686
+ properties: None,
687
+ predicate: None,
688
+ };
689
+ let node_b = NodePattern {
690
+ variable: None,
691
+ labels: LabelExpression::None,
692
+ properties: None,
693
+ predicate: None,
694
+ };
695
+
696
+ let qp_source = node_a.clone();
697
+ let qp_destination = node_b.clone();
698
+ let edge_pattern = EdgePattern {
699
+ variable: None,
700
+ source: node_a,
701
+ destination: node_b,
702
+ directivity: graphcore::EdgeDirectivity::Directed,
703
+ labels: LabelExpression::String("R".into()),
704
+ properties: None,
705
+ predicate: None,
706
+ };
707
+
708
+ let quantified_pattern = Pattern::QuantifiedPath(QuantifiedPathPattern {
709
+ variable: None,
710
+ source: qp_source,
711
+ group: vec![Pattern::Edge(edge_pattern)],
712
+ group_where: None,
713
+ path_where: None,
714
+ quantifier: Some(1..3),
715
+ destination: qp_destination,
716
+ selector: PathSelector::Shortest(Value { value: 1.into() }.into()),
717
+ });
718
+
719
+ let planned = planner::plan_pattern(quantified_pattern);
720
+ assert!(matches!(
721
+ planned,
722
+ planner::PlannedPattern::QuantifiedPath { strategy, .. }
723
+ if matches!(strategy, planner::PathStrategy::Bfs { stop_condition: planner::StopCondition::Shortest(_) })
724
+ ));
725
+ }
726
+
727
+ #[test]
728
+ fn test_wrapper_around_plain_edge_promotes_to_degenerate_quantifier()
729
+ {
730
+ let identifiers = VariableIdentifiers::default();
731
+ let node_a = NodePattern {
732
+ variable: Some(identifiers.create_variable_from_name("a")),
733
+ labels: LabelExpression::None,
734
+ properties: None,
735
+ predicate: None,
736
+ };
737
+ let node_b = NodePattern {
738
+ variable: None,
739
+ labels: LabelExpression::None,
740
+ properties: None,
741
+ predicate: None,
742
+ };
743
+
744
+ let qp_source = node_a.clone();
745
+ let qp_destination = node_b.clone();
746
+ let edge_pattern = EdgePattern {
747
+ variable: None,
748
+ source: node_a,
749
+ destination: node_b,
750
+ directivity: graphcore::EdgeDirectivity::Directed,
751
+ labels: LabelExpression::String("R".into()),
752
+ properties: None,
753
+ predicate: None,
754
+ };
755
+
756
+ // Wrapper pattern with quantifier: None (the scoping wrapper)
757
+ let wrapper = QuantifiedPathPattern {
758
+ variable: None,
759
+ source: qp_source,
760
+ group: vec![Pattern::Edge(edge_pattern)],
761
+ group_where: None,
762
+ path_where: None,
763
+ quantifier: None,
764
+ destination: qp_destination,
765
+ selector: PathSelector::Shortest(Value { value: 1.into() }.into()),
766
+ };
767
+
768
+ let planned = planner::plan_pattern(Pattern::QuantifiedPath(wrapper));
769
+
770
+ // Assert: the result should be a QuantifiedPath with promoted quantifier and outer strategy
771
+ match planned
772
+ {
773
+ planner::PlannedPattern::QuantifiedPath { pattern, strategy } =>
774
+ {
775
+ // Check quantifier was promoted to exactly-one repetition (1..1)
776
+ assert_eq!(
777
+ pattern.quantifier,
778
+ Some(1..1),
779
+ "quantifier should be promoted to Some(1..1)"
780
+ );
781
+
782
+ // Check strategy matches the outer selector (Shortest with literal 1)
783
+ assert!(
784
+ matches!(
785
+ strategy,
786
+ planner::PathStrategy::Bfs {
787
+ stop_condition: planner::StopCondition::Shortest(_)
788
+ }
789
+ ),
790
+ "strategy should be Bfs with Shortest stop condition"
791
+ );
792
+ }
793
+ _ => panic!("expected PlannedPattern::QuantifiedPath"),
794
+ }
795
+ }
796
+
797
+ #[test]
798
+ fn test_wrapper_around_quantified_path_preserves_inner_quantifier_and_applies_outer_strategy()
799
+ {
800
+ let identifiers = VariableIdentifiers::default();
801
+ let node_a = NodePattern {
802
+ variable: Some(identifiers.create_variable_from_name("a")),
803
+ labels: LabelExpression::None,
804
+ properties: None,
805
+ predicate: None,
806
+ };
807
+ let node_b = NodePattern {
808
+ variable: None,
809
+ labels: LabelExpression::None,
810
+ properties: None,
811
+ predicate: None,
812
+ };
813
+
814
+ let qp_source = node_a.clone();
815
+ let qp_destination = node_b.clone();
816
+ let edge_pattern = EdgePattern {
817
+ variable: None,
818
+ source: node_a,
819
+ destination: node_b,
820
+ directivity: graphcore::EdgeDirectivity::Directed,
821
+ labels: LabelExpression::String("R".into()),
822
+ properties: None,
823
+ predicate: None,
824
+ };
825
+
826
+ // Inner already-quantified pattern (simulating `--+`)
827
+ let inner_quantified = QuantifiedPathPattern {
828
+ variable: None,
829
+ source: qp_source.clone(),
830
+ group: vec![Pattern::Edge(edge_pattern)],
831
+ group_where: None,
832
+ path_where: None,
833
+ quantifier: Some(1..usize::MAX),
834
+ destination: qp_destination.clone(),
835
+ selector: PathSelector::All,
836
+ };
837
+
838
+ // Outer wrapper with quantifier: None (scoping wrapper)
839
+ let wrapper = QuantifiedPathPattern {
840
+ variable: None,
841
+ source: qp_source,
842
+ group: vec![Pattern::QuantifiedPath(inner_quantified)],
843
+ group_where: None,
844
+ path_where: None,
845
+ quantifier: None,
846
+ destination: qp_destination,
847
+ selector: PathSelector::Shortest(Value { value: 1.into() }.into()),
848
+ };
849
+
850
+ let planned = planner::plan_pattern(Pattern::QuantifiedPath(wrapper));
851
+
852
+ // Assert: inner quantifier is preserved, but strategy is from outer selector
853
+ match planned
854
+ {
855
+ planner::PlannedPattern::QuantifiedPath { pattern, strategy } =>
856
+ {
857
+ // Check inner quantifier was preserved (1..usize::MAX)
858
+ assert_eq!(
859
+ pattern.quantifier,
860
+ Some(1..usize::MAX),
861
+ "inner quantifier should be preserved"
862
+ );
863
+
864
+ // Check strategy is from outer selector, not inner's All/Dfs
865
+ assert!(
866
+ matches!(
867
+ strategy,
868
+ planner::PathStrategy::Bfs {
869
+ stop_condition: planner::StopCondition::Shortest(_)
870
+ }
871
+ ),
872
+ "strategy should be Bfs (from outer), not Dfs (from inner All)"
873
+ );
874
+ }
875
+ _ => panic!("expected PlannedPattern::QuantifiedPath"),
876
+ }
877
+ }
878
+
879
+ #[test]
880
+ fn test_wrapper_with_group_where_is_preserved()
881
+ {
882
+ let identifiers = VariableIdentifiers::default();
883
+ let node_a = NodePattern {
884
+ variable: Some(identifiers.create_variable_from_name("a")),
885
+ labels: LabelExpression::None,
886
+ properties: None,
887
+ predicate: None,
888
+ };
889
+ let node_b = NodePattern {
890
+ variable: None,
891
+ labels: LabelExpression::None,
892
+ properties: None,
893
+ predicate: None,
894
+ };
895
+
896
+ let qp_source = node_a.clone();
897
+ let qp_destination = node_b.clone();
898
+ let edge_pattern = EdgePattern {
899
+ variable: None,
900
+ source: node_a,
901
+ destination: node_b,
902
+ directivity: graphcore::EdgeDirectivity::Directed,
903
+ labels: LabelExpression::String("R".into()),
904
+ properties: None,
905
+ predicate: None,
906
+ };
907
+
908
+ // Group where condition - using a simple Value expression
909
+ let group_where = Some(Value { value: true.into() }.into());
910
+
911
+ // Wrapper pattern with quantifier: None and group_where
912
+ let wrapper = QuantifiedPathPattern {
913
+ variable: None,
914
+ source: qp_source,
915
+ group: vec![Pattern::Edge(edge_pattern)],
916
+ group_where: group_where.clone(),
917
+ path_where: None,
918
+ quantifier: None,
919
+ destination: qp_destination,
920
+ selector: PathSelector::Shortest(Value { value: 1.into() }.into()),
921
+ };
922
+
923
+ let planned = planner::plan_pattern(Pattern::QuantifiedPath(wrapper));
924
+
925
+ // Assert: the result should have path_where set (wrapper predicate routes to path_where)
926
+ // and group_where should be None (wrapper-promoted patterns never have group_where)
927
+ match planned
928
+ {
929
+ planner::PlannedPattern::QuantifiedPath { pattern, .. } =>
930
+ {
931
+ assert!(
932
+ pattern.path_where.is_some(),
933
+ "path_where should contain the wrapper's predicate in the promoted pattern"
934
+ );
935
+ assert!(
936
+ pattern.group_where.is_none(),
937
+ "group_where should be None in wrapper-promoted patterns (predicates route to path_where)"
938
+ );
939
+ }
940
+ _ => panic!("expected PlannedPattern::QuantifiedPath"),
941
+ }
942
+ }
@@ -34,7 +34,7 @@ mod reopen
34
34
  #[test]
35
35
  fn test_postgres_node_update_syncs_to_vector_index()
36
36
  {
37
- let db = create_tmp_db(274);
37
+ let db = create_tmp_db(290);
38
38
  let config: postgres::Config = db.connection_uri().parse().unwrap();
39
39
  let store = crate::store::postgres::Store::connect(config).unwrap();
40
40
  super::test_vector_updated_on_node_property_update(store);
@@ -43,7 +43,7 @@ fn test_postgres_node_update_syncs_to_vector_index()
43
43
  #[test]
44
44
  fn test_postgres_upsert_rejects_nan_component()
45
45
  {
46
- let db = create_tmp_db(275);
46
+ let db = create_tmp_db(291);
47
47
  let config: postgres::Config = db.connection_uri().parse().unwrap();
48
48
  let store = crate::store::postgres::Store::connect(config).unwrap();
49
49
 
@@ -79,7 +79,7 @@ fn test_postgres_upsert_rejects_nan_component()
79
79
  #[test]
80
80
  fn test_postgres_upsert_rejects_inf_component()
81
81
  {
82
- let db = create_tmp_db(276);
82
+ let db = create_tmp_db(292);
83
83
  let config: postgres::Config = db.connection_uri().parse().unwrap();
84
84
  let store = crate::store::postgres::Store::connect(config).unwrap();
85
85
 
@@ -115,7 +115,7 @@ fn test_postgres_upsert_rejects_inf_component()
115
115
  #[test]
116
116
  fn test_postgres_search_rejects_nan_query()
117
117
  {
118
- let db = create_tmp_db(277);
118
+ let db = create_tmp_db(293);
119
119
  let config: postgres::Config = db.connection_uri().parse().unwrap();
120
120
  let store = crate::store::postgres::Store::connect(config).unwrap();
121
121
 
@@ -153,7 +153,7 @@ fn test_postgres_search_rejects_nan_query()
153
153
  #[test]
154
154
  fn test_postgres_delete_vectors_rejects_malformed_node_id()
155
155
  {
156
- let db = create_tmp_db(278);
156
+ let db = create_tmp_db(294);
157
157
  let config: postgres::Config = db.connection_uri().parse().unwrap();
158
158
  let store = crate::store::postgres::Store::connect(config).unwrap();
159
159
 
@@ -33,7 +33,9 @@ fn test_sqlite_vector_binary_roundtrip()
33
33
  let blob: Vec<u8> = original.iter().flat_map(|f| f.to_le_bytes()).collect();
34
34
 
35
35
  let decoded: Vec<f32> = blob
36
- .chunks_exact(4)
36
+ .as_chunks::<4>()
37
+ .0
38
+ .iter()
37
39
  .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
38
40
  .collect();
39
41