gqlite 1.8.1 → 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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +8 -8
  3. data/ext/gqlitedb/Cargo.toml +0 -2
  4. data/ext/gqlitedb/src/compiler/variables_manager.rs +109 -10
  5. data/ext/gqlitedb/src/compiler.rs +481 -47
  6. data/ext/gqlitedb/src/connection.rs +12 -0
  7. data/ext/gqlitedb/src/consts.rs +1 -1
  8. data/ext/gqlitedb/src/error.rs +24 -0
  9. data/ext/gqlitedb/src/interpreter/evaluators.rs +91 -325
  10. data/ext/gqlitedb/src/interpreter/instructions.rs +72 -0
  11. data/ext/gqlitedb/src/interpreter/mod.rs +1 -0
  12. data/ext/gqlitedb/src/interpreter/path/bfs.rs +494 -0
  13. data/ext/gqlitedb/src/interpreter/path/dfs.rs +267 -0
  14. data/ext/gqlitedb/src/interpreter/path/group.rs +418 -0
  15. data/ext/gqlitedb/src/interpreter/path.rs +542 -0
  16. data/ext/gqlitedb/src/planner.rs +218 -6
  17. data/ext/gqlitedb/src/query_result.rs +19 -4
  18. data/ext/gqlitedb/src/tests/compiler.rs +2453 -1
  19. data/ext/gqlitedb/src/tests/connection/redb.rs +8 -0
  20. data/ext/gqlitedb/src/tests/connection.rs +37 -0
  21. data/ext/gqlitedb/src/tests/evaluators.rs +2206 -1
  22. data/ext/gqlitedb/src/tests/parser.rs +1225 -0
  23. data/ext/gqlitedb/src/tests/planner.rs +432 -2
  24. data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +5 -5
  25. data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +3 -1
  26. data/ext/gqlitedb/src/tests/templates/ast.rs +284 -1
  27. data/ext/gqlitedb/src/tests/templates/programs.rs +306 -0
  28. data/ext/gqliterb/Cargo.toml +0 -1
  29. data/ext/gqlparser/src/oc/ast.rs +52 -3
  30. data/ext/gqlparser/src/oc/error.rs +18 -0
  31. data/ext/gqlparser/src/oc/lexer.rs +146 -0
  32. data/ext/gqlparser/src/oc/parser/tests.rs +365 -20
  33. data/ext/gqlparser/src/oc/parser.rs +1196 -143
  34. data/ext/graphcore/src/value.rs +12 -1
  35. metadata +5 -2
  36. 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"
@@ -221,6 +223,8 @@ fn test_plan_maps_direct_statements()
221
223
  variable: Some(node_identifier.clone()),
222
224
  labels: LabelExpression::None,
223
225
  properties: None,
226
+
227
+ predicate: None,
224
228
  })],
225
229
  }
226
230
  .into(),
@@ -340,6 +344,8 @@ fn test_plan_match_with_search_emits_vector_search()
340
344
  variable: Some(node_identifier.clone()),
341
345
  labels: LabelExpression::String("Doc".into()),
342
346
  properties: None,
347
+
348
+ predicate: None,
343
349
  })],
344
350
  where_expression: None,
345
351
  optional: false,
@@ -391,6 +397,8 @@ fn test_plan_match_with_search_and_score()
391
397
  variable: Some(node_identifier.clone()),
392
398
  labels: LabelExpression::String("Doc".into()),
393
399
  properties: None,
400
+
401
+ predicate: None,
394
402
  })],
395
403
  where_expression: None,
396
404
  optional: false,
@@ -440,6 +448,8 @@ fn test_plan_match_without_search_still_emits_node_scan()
440
448
  variable: Some(node_identifier.clone()),
441
449
  labels: LabelExpression::String("Person".into()),
442
450
  properties: None,
451
+
452
+ predicate: None,
443
453
  })],
444
454
  where_expression: None,
445
455
  optional: false,
@@ -457,7 +467,7 @@ fn test_plan_match_without_search_still_emits_node_scan()
457
467
  }
458
468
  if matches!(
459
469
  &patterns[0],
460
- Pattern::Node(NodePattern {
470
+ planner::PlannedPattern::Node(NodePattern {
461
471
  labels: LabelExpression::String(label),
462
472
  ..
463
473
  }) if label == "Person"
@@ -477,6 +487,8 @@ fn test_plan_match_with_search_and_filter()
477
487
  variable: Some(node_identifier.clone()),
478
488
  labels: LabelExpression::String("Doc".into()),
479
489
  properties: None,
490
+
491
+ predicate: None,
480
492
  })],
481
493
  where_expression: Some(Value { value: true.into() }.into()),
482
494
  optional: false,
@@ -510,3 +522,421 @@ fn test_plan_match_with_search_and_filter()
510
522
  && *k == 3
511
523
  ));
512
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