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
@@ -421,7 +421,7 @@ fn test_oc_parse_relational_binary_operator()
421
421
  #[test]
422
422
  fn test_oc_parse_node_pattern_simple()
423
423
  {
424
- let actual = parse("(n:Person)", node_pattern);
424
+ let actual = parse("(n:Person)", node_pattern::<AllowUndirected>);
425
425
 
426
426
  let expected = ast::NodePattern {
427
427
  variable: ast::VariableIdentifier {
@@ -431,6 +431,8 @@ fn test_oc_parse_node_pattern_simple()
431
431
  .into(),
432
432
  labels: ast::LabelExpression::String("Person".to_string()),
433
433
  properties: None,
434
+
435
+ predicate: None,
434
436
  };
435
437
  compare_ast(actual, expected);
436
438
  }
@@ -438,7 +440,7 @@ fn test_oc_parse_node_pattern_simple()
438
440
  #[test]
439
441
  fn test_oc_parse_node_pattern_parameters()
440
442
  {
441
- let node = parse("(n {id: 12, name: 'foo'})", node_pattern);
443
+ let node = parse("(n {id: 12, name: 'foo'})", node_pattern::<AllowUndirected>);
442
444
 
443
445
  let expected_node = ast::NodePattern {
444
446
  variable: ast::VariableIdentifier {
@@ -461,11 +463,16 @@ fn test_oc_parse_node_pattern_parameters()
461
463
  ),
462
464
  ],
463
465
  })),
466
+
467
+ predicate: None,
464
468
  };
465
469
 
466
470
  compare_ast(node, expected_node);
467
471
 
468
- let node = parse(r#"(n {id: "12", name: "foo"})"#, node_pattern);
472
+ let node = parse(
473
+ r#"(n {id: "12", name: "foo"})"#,
474
+ node_pattern::<AllowUndirected>,
475
+ );
469
476
 
470
477
  let expected_node = ast::NodePattern {
471
478
  variable: ast::VariableIdentifier {
@@ -488,6 +495,8 @@ fn test_oc_parse_node_pattern_parameters()
488
495
  ),
489
496
  ],
490
497
  })),
498
+
499
+ predicate: None,
491
500
  };
492
501
 
493
502
  compare_ast(node, expected_node);
@@ -509,6 +518,7 @@ fn test_oc_parse_edge_patterns()
509
518
  .into(),
510
519
  labels: ast::LabelExpression::None,
511
520
  properties: None,
521
+ predicate: None,
512
522
  },
513
523
  variable: ast::VariableIdentifier {
514
524
  name: "m".to_string(),
@@ -523,11 +533,13 @@ fn test_oc_parse_edge_patterns()
523
533
  .into(),
524
534
  labels: ast::LabelExpression::None,
525
535
  properties: None,
536
+ predicate: None,
526
537
  },
527
538
  directivity: graphcore::EdgeDirectivity::Directed,
528
539
  labels: ast::LabelExpression::None,
529
540
  properties: None,
530
- recursive_range: None,
541
+
542
+ predicate: None,
531
543
  }
532
544
  .into(),
533
545
  ],
@@ -562,7 +574,8 @@ fn test_oc_parse_edge_recursive_patterns()
562
574
  "(n)-[m*1..2]->(p)",
563
575
  patterns::<AllowUndirected>,
564
576
  vec![
565
- ast::EdgePattern {
577
+ ast::QuantifiedPathPattern {
578
+ variable: None,
566
579
  source: ast::NodePattern {
567
580
  variable: ast::VariableIdentifier {
568
581
  name: "n".to_string(),
@@ -571,12 +584,45 @@ fn test_oc_parse_edge_recursive_patterns()
571
584
  .into(),
572
585
  labels: ast::LabelExpression::None,
573
586
  properties: None,
587
+ predicate: None,
574
588
  },
575
- variable: ast::VariableIdentifier {
576
- name: "m".to_string(),
577
- id: 2,
578
- }
579
- .into(),
589
+ group: vec![
590
+ ast::EdgePattern {
591
+ source: ast::NodePattern {
592
+ variable: ast::VariableIdentifier {
593
+ name: "n".to_string(),
594
+ id: 0,
595
+ }
596
+ .into(),
597
+ labels: ast::LabelExpression::None,
598
+ properties: None,
599
+ predicate: None,
600
+ },
601
+ variable: ast::VariableIdentifier {
602
+ name: "m".to_string(),
603
+ id: 2,
604
+ }
605
+ .into(),
606
+ destination: ast::NodePattern {
607
+ variable: ast::VariableIdentifier {
608
+ name: "p".to_string(),
609
+ id: 1,
610
+ }
611
+ .into(),
612
+ labels: ast::LabelExpression::None,
613
+ properties: None,
614
+ predicate: None,
615
+ },
616
+ directivity: graphcore::EdgeDirectivity::Directed,
617
+ labels: ast::LabelExpression::None,
618
+ properties: None,
619
+ predicate: None,
620
+ }
621
+ .into(),
622
+ ],
623
+ group_where: None,
624
+ path_where: None,
625
+ quantifier: Some(1..2),
580
626
  destination: ast::NodePattern {
581
627
  variable: ast::VariableIdentifier {
582
628
  name: "p".to_string(),
@@ -585,11 +631,9 @@ fn test_oc_parse_edge_recursive_patterns()
585
631
  .into(),
586
632
  labels: ast::LabelExpression::None,
587
633
  properties: None,
634
+ predicate: None,
588
635
  },
589
- directivity: graphcore::EdgeDirectivity::Directed,
590
- labels: ast::LabelExpression::None,
591
- properties: None,
592
- recursive_range: Some(1..2),
636
+ selector: ast::PathSelector::All,
593
637
  }
594
638
  .into(),
595
639
  ],
@@ -763,7 +807,7 @@ fn test_oc_parse_string_literal()
763
807
  #[test]
764
808
  fn test_oc_parse_node_pattern_with_parameter_properties()
765
809
  {
766
- let node = parse("(n $props)", node_pattern);
810
+ let node = parse("(n $props)", node_pattern::<AllowUndirected>);
767
811
 
768
812
  let expr = node.properties.expect("properties expected");
769
813
 
@@ -789,6 +833,8 @@ fn test_oc_parse_match_statement_without_where()
789
833
  .into(),
790
834
  labels: ast::LabelExpression::None,
791
835
  properties: None,
836
+
837
+ predicate: None,
792
838
  }
793
839
  .into(),
794
840
  ],
@@ -815,6 +861,8 @@ fn test_oc_parse_optional_match_statement_without_where()
815
861
  .into(),
816
862
  labels: ast::LabelExpression::None,
817
863
  properties: None,
864
+
865
+ predicate: None,
818
866
  }
819
867
  .into(),
820
868
  ],
@@ -838,6 +886,7 @@ fn test_oc_parse_match_statement_double_edge()
838
886
  variable: None,
839
887
  labels: ast::LabelExpression::None,
840
888
  properties: None,
889
+ predicate: None,
841
890
  },
842
891
  destination: ast::NodePattern {
843
892
  variable: Some(ast::VariableIdentifier {
@@ -846,12 +895,14 @@ fn test_oc_parse_match_statement_double_edge()
846
895
  }),
847
896
  labels: ast::LabelExpression::None,
848
897
  properties: None,
898
+ predicate: None,
849
899
  },
850
900
  variable: None,
851
901
  directivity: graphcore::EdgeDirectivity::Directed,
852
902
  labels: ast::LabelExpression::None,
853
903
  properties: None,
854
- recursive_range: None,
904
+
905
+ predicate: None,
855
906
  }
856
907
  .into(),
857
908
  ast::EdgePattern {
@@ -862,17 +913,20 @@ fn test_oc_parse_match_statement_double_edge()
862
913
  }),
863
914
  labels: ast::LabelExpression::None,
864
915
  properties: None,
916
+ predicate: None,
865
917
  },
866
918
  destination: ast::NodePattern {
867
919
  variable: None,
868
920
  labels: ast::LabelExpression::None,
869
921
  properties: None,
922
+ predicate: None,
870
923
  },
871
924
  variable: None,
872
925
  directivity: graphcore::EdgeDirectivity::Directed,
873
926
  labels: ast::LabelExpression::None,
874
927
  properties: None,
875
- recursive_range: None,
928
+
929
+ predicate: None,
876
930
  }
877
931
  .into(),
878
932
  ],
@@ -905,6 +959,8 @@ fn test_oc_parse_merge_basic_pattern()
905
959
  }),
906
960
  )],
907
961
  })),
962
+
963
+ predicate: None,
908
964
  }
909
965
  .into(),
910
966
  ],
@@ -929,6 +985,8 @@ fn test_oc_parse_merge_with_on_create_set()
929
985
  }),
930
986
  labels: ast::LabelExpression::String("Person".to_string()),
931
987
  properties: None,
988
+
989
+ predicate: None,
932
990
  }
933
991
  .into(),
934
992
  ],
@@ -960,6 +1018,8 @@ fn test_oc_parse_merge_with_on_match_set()
960
1018
  }),
961
1019
  labels: ast::LabelExpression::String("Person".to_string()),
962
1020
  properties: None,
1021
+
1022
+ predicate: None,
963
1023
  }
964
1024
  .into(),
965
1025
  ],
@@ -994,6 +1054,8 @@ fn test_oc_parse_merge_with_both_on_clauses()
994
1054
  }),
995
1055
  labels: ast::LabelExpression::String("Person".to_string()),
996
1056
  properties: None,
1057
+
1058
+ predicate: None,
997
1059
  }
998
1060
  .into(),
999
1061
  ],
@@ -1035,6 +1097,8 @@ fn test_oc_parse_merge_with_both_on_clauses_reversed()
1035
1097
  }),
1036
1098
  labels: ast::LabelExpression::String("Person".to_string()),
1037
1099
  properties: None,
1100
+
1101
+ predicate: None,
1038
1102
  }
1039
1103
  .into(),
1040
1104
  ],
@@ -1074,6 +1138,7 @@ fn test_oc_parse_merge_with_edge_pattern()
1074
1138
  }),
1075
1139
  labels: ast::LabelExpression::None,
1076
1140
  properties: None,
1141
+ predicate: None,
1077
1142
  },
1078
1143
  destination: ast::NodePattern {
1079
1144
  variable: Some(ast::VariableIdentifier {
@@ -1082,12 +1147,14 @@ fn test_oc_parse_merge_with_edge_pattern()
1082
1147
  }),
1083
1148
  labels: ast::LabelExpression::None,
1084
1149
  properties: None,
1150
+ predicate: None,
1085
1151
  },
1086
1152
  variable: None,
1087
1153
  labels: ast::LabelExpression::String("KNOWS".to_string()),
1088
1154
  properties: None,
1089
1155
  directivity: graphcore::EdgeDirectivity::Directed,
1090
- recursive_range: None,
1156
+
1157
+ predicate: None,
1091
1158
  }
1092
1159
  .into(),
1093
1160
  ],
@@ -1114,6 +1181,8 @@ fn test_oc_parse_merge_statement()
1114
1181
  .into(),
1115
1182
  labels: ast::LabelExpression::None,
1116
1183
  properties: None,
1184
+
1185
+ predicate: None,
1117
1186
  }
1118
1187
  .into(),
1119
1188
  ],
@@ -1128,6 +1197,8 @@ fn test_oc_parse_merge_statement()
1128
1197
  variable: None,
1129
1198
  labels: ast::LabelExpression::None,
1130
1199
  properties: None,
1200
+
1201
+ predicate: None,
1131
1202
  }
1132
1203
  .into(),
1133
1204
  ],
@@ -1142,6 +1213,8 @@ fn test_oc_parse_merge_statement()
1142
1213
  variable: None,
1143
1214
  labels: ast::LabelExpression::String("A".to_string()),
1144
1215
  properties: None,
1216
+
1217
+ predicate: None,
1145
1218
  }
1146
1219
  .into(),
1147
1220
  ],
@@ -1159,6 +1232,8 @@ fn test_oc_parse_merge_statement()
1159
1232
  ast::LabelExpression::String("B".to_string()),
1160
1233
  ]),
1161
1234
  properties: None,
1235
+
1236
+ predicate: None,
1162
1237
  }
1163
1238
  .into(),
1164
1239
  ],
@@ -1179,17 +1254,20 @@ fn test_oc_parse_create_edge_statements()
1179
1254
  variable: None,
1180
1255
  labels: ast::LabelExpression::None,
1181
1256
  properties: None,
1257
+ predicate: None,
1182
1258
  },
1183
1259
  destination: ast::NodePattern {
1184
1260
  variable: None,
1185
1261
  labels: ast::LabelExpression::None,
1186
1262
  properties: None,
1263
+ predicate: None,
1187
1264
  },
1188
1265
  variable: None,
1189
1266
  directivity: graphcore::EdgeDirectivity::Directed,
1190
1267
  labels: ast::LabelExpression::String("R".to_string()),
1191
1268
  properties: None,
1192
- recursive_range: None,
1269
+
1270
+ predicate: None,
1193
1271
  }
1194
1272
  .into(),
1195
1273
  ],
@@ -1208,6 +1286,7 @@ fn test_oc_parse_create_edge_statements()
1208
1286
  }),
1209
1287
  labels: ast::LabelExpression::None,
1210
1288
  properties: None,
1289
+ predicate: None,
1211
1290
  },
1212
1291
  destination: ast::NodePattern {
1213
1292
  variable: Some(ast::VariableIdentifier {
@@ -1216,12 +1295,14 @@ fn test_oc_parse_create_edge_statements()
1216
1295
  }),
1217
1296
  labels: ast::LabelExpression::None,
1218
1297
  properties: None,
1298
+ predicate: None,
1219
1299
  },
1220
1300
  variable: None,
1221
1301
  directivity: graphcore::EdgeDirectivity::Directed,
1222
1302
  labels: ast::LabelExpression::String("FOO".to_string()),
1223
1303
  properties: None,
1224
- recursive_range: None,
1304
+
1305
+ predicate: None,
1225
1306
  }
1226
1307
  .into(),
1227
1308
  ],
@@ -1726,6 +1807,8 @@ fn test_oc_parse_queries_multiple_statements()
1726
1807
  .into(),
1727
1808
  labels: ast::LabelExpression::None,
1728
1809
  properties: None,
1810
+
1811
+ predicate: None,
1729
1812
  }
1730
1813
  .into(),
1731
1814
  ],
@@ -1765,6 +1848,8 @@ fn test_oc_parse_queries_multiple_queries()
1765
1848
  .into(),
1766
1849
  labels: ast::LabelExpression::None,
1767
1850
  properties: None,
1851
+
1852
+ predicate: None,
1768
1853
  }
1769
1854
  .into(),
1770
1855
  ],
@@ -1807,6 +1892,8 @@ fn test_oc_parse_query_union_basic()
1807
1892
  .into(),
1808
1893
  labels: ast::LabelExpression::None,
1809
1894
  properties: None,
1895
+
1896
+ predicate: None,
1810
1897
  }
1811
1898
  .into(),
1812
1899
  ],
@@ -1827,6 +1914,8 @@ fn test_oc_parse_query_union_basic()
1827
1914
  .into(),
1828
1915
  labels: ast::LabelExpression::None,
1829
1916
  properties: None,
1917
+
1918
+ predicate: None,
1830
1919
  }
1831
1920
  .into(),
1832
1921
  ],
@@ -1861,6 +1950,8 @@ fn test_oc_parse_query_union_all_basic()
1861
1950
  .into(),
1862
1951
  labels: ast::LabelExpression::None,
1863
1952
  properties: None,
1953
+
1954
+ predicate: None,
1864
1955
  }
1865
1956
  .into(),
1866
1957
  ],
@@ -1881,6 +1972,8 @@ fn test_oc_parse_query_union_all_basic()
1881
1972
  .into(),
1882
1973
  labels: ast::LabelExpression::None,
1883
1974
  properties: None,
1975
+
1976
+ predicate: None,
1884
1977
  }
1885
1978
  .into(),
1886
1979
  ],
@@ -1915,6 +2008,8 @@ fn test_oc_parse_query_chained_union_basic()
1915
2008
  .into(),
1916
2009
  labels: ast::LabelExpression::None,
1917
2010
  properties: None,
2011
+
2012
+ predicate: None,
1918
2013
  }
1919
2014
  .into(),
1920
2015
  ],
@@ -1935,6 +2030,8 @@ fn test_oc_parse_query_chained_union_basic()
1935
2030
  .into(),
1936
2031
  labels: ast::LabelExpression::None,
1937
2032
  properties: None,
2033
+
2034
+ predicate: None,
1938
2035
  }
1939
2036
  .into(),
1940
2037
  ],
@@ -1955,6 +2052,8 @@ fn test_oc_parse_query_chained_union_basic()
1955
2052
  .into(),
1956
2053
  labels: ast::LabelExpression::None,
1957
2054
  properties: None,
2055
+
2056
+ predicate: None,
1958
2057
  }
1959
2058
  .into(),
1960
2059
  ],
@@ -1987,6 +2086,8 @@ fn test_oc_parse_query_plain()
1987
2086
  .into(),
1988
2087
  labels: ast::LabelExpression::None,
1989
2088
  properties: None,
2089
+
2090
+ predicate: None,
1990
2091
  }
1991
2092
  .into(),
1992
2093
  ],
@@ -2151,6 +2252,19 @@ fn test_oc_call_internal_stats()
2151
2252
  );
2152
2253
  }
2153
2254
 
2255
+ #[test]
2256
+ fn test_oc_call_db_list_graphs()
2257
+ {
2258
+ compare_parse_to_ast(
2259
+ "CALL gqlite.db.list_graphs()",
2260
+ call_statement,
2261
+ ast::Call {
2262
+ name: "gqlite.db.list_graphs".to_string(),
2263
+ arguments: vec![],
2264
+ },
2265
+ );
2266
+ }
2267
+
2154
2268
  #[test]
2155
2269
  fn test_oc_parse_match_with_search_basic()
2156
2270
  {
@@ -2643,3 +2757,234 @@ fn test_oc_parse_function_call_distinct_with_multiple_args()
2643
2757
  },
2644
2758
  );
2645
2759
  }
2760
+
2761
+ // Tests for work item #569: node_pattern predicate parsing
2762
+
2763
+ #[test]
2764
+ fn test_oc_parse_node_pattern_with_predicate_and_label()
2765
+ {
2766
+ // Test parsing (n:Station WHERE n.name = 'X')
2767
+ let actual = parse(
2768
+ "(n:Station WHERE n.name = 'X')",
2769
+ node_pattern::<AllowUndirected>,
2770
+ );
2771
+
2772
+ // Verify predicate is present
2773
+ assert!(actual.predicate.is_some(), "expected predicate to be Some");
2774
+
2775
+ // Verify label is correct
2776
+ assert!(matches!(actual.labels, ast::LabelExpression::String(ref s) if s == "Station"));
2777
+ }
2778
+
2779
+ #[test]
2780
+ fn test_oc_parse_node_pattern_with_predicate_no_label()
2781
+ {
2782
+ // Test parsing (n WHERE n.name = 'X')
2783
+ let actual = parse("(n WHERE n.name = 'X')", node_pattern::<AllowUndirected>);
2784
+
2785
+ // Verify predicate is present
2786
+ assert!(actual.predicate.is_some(), "expected predicate to be Some");
2787
+
2788
+ // Verify label is None
2789
+ assert!(matches!(actual.labels, ast::LabelExpression::None));
2790
+ }
2791
+
2792
+ #[test]
2793
+ fn test_oc_parse_node_pattern_with_predicate_and_properties()
2794
+ {
2795
+ // Test parsing (n:Station {zone: 2} WHERE n.name STARTS WITH 'D')
2796
+ let actual = parse(
2797
+ "(n:Station {zone: 2} WHERE n.name STARTS WITH 'D')",
2798
+ node_pattern::<AllowUndirected>,
2799
+ );
2800
+
2801
+ // Verify both properties and predicate are present
2802
+ assert!(
2803
+ actual.properties.is_some(),
2804
+ "expected properties to be Some"
2805
+ );
2806
+ assert!(actual.predicate.is_some(), "expected predicate to be Some");
2807
+
2808
+ // Verify label is correct
2809
+ assert!(matches!(actual.labels, ast::LabelExpression::String(ref s) if s == "Station"));
2810
+ }
2811
+
2812
+ #[test]
2813
+ fn test_oc_parse_node_pattern_without_predicate_regression()
2814
+ {
2815
+ // Regression test: ensure predicate remains None when not specified
2816
+ let actual = parse("(n)", node_pattern::<AllowUndirected>);
2817
+
2818
+ // Verify predicate is None
2819
+ assert!(actual.predicate.is_none(), "expected predicate to be None");
2820
+ }
2821
+
2822
+ #[test]
2823
+ fn test_oc_parse_create_with_predicate_rejected()
2824
+ {
2825
+ // Test: CREATE patterns with WHERE predicates should be rejected
2826
+ // This is parsed with DisallowUndirected context (used for CREATE)
2827
+ parse_fail(
2828
+ "(n:Station WHERE n.name = 'X')",
2829
+ node_pattern::<DisallowUndirected>,
2830
+ |error_kind| *error_kind == ErrorKind::CreatingWithPredicate,
2831
+ );
2832
+ }
2833
+
2834
+ #[test]
2835
+ fn test_oc_parse_group_pattern_simple()
2836
+ {
2837
+ // Test parsing ((n)-[r:KNOWS]->(m)){1,5}(b)
2838
+ let actual = parse(
2839
+ "((n)-[r:KNOWS]->(m)){1,5}(b)",
2840
+ group_pattern::<AllowUndirected>,
2841
+ );
2842
+
2843
+ // Verify group contains exactly one pattern (the edge)
2844
+ assert_eq!(
2845
+ actual.group.len(),
2846
+ 1,
2847
+ "Expected one pattern in group, got {}",
2848
+ actual.group.len()
2849
+ );
2850
+
2851
+ // Verify the group contains an Edge pattern
2852
+ assert!(
2853
+ matches!(&actual.group[0], ast::Pattern::Edge(_)),
2854
+ "Expected Edge pattern in group"
2855
+ );
2856
+
2857
+ // Verify quantifier is 1..5
2858
+ assert_eq!(
2859
+ actual.quantifier,
2860
+ 1..5,
2861
+ "Expected quantifier 1..5, got {:?}",
2862
+ actual.quantifier
2863
+ );
2864
+
2865
+ // Verify destination_node is bound to variable 'b'
2866
+ assert!(
2867
+ actual.destination_node.variable.is_some(),
2868
+ "Expected destination_node to have a variable"
2869
+ );
2870
+ if let Some(var) = &actual.destination_node.variable
2871
+ {
2872
+ assert_eq!(
2873
+ var.name(),
2874
+ "b",
2875
+ "Expected destination_node variable to be 'b', got '{}'",
2876
+ var.name()
2877
+ );
2878
+ }
2879
+ }
2880
+
2881
+ #[test]
2882
+ fn test_oc_parse_selector_scoped_pattern_basic()
2883
+ {
2884
+ // Test parsing selector-scoped pattern: ((n)-[r]->(m) WHERE n.x > 1)
2885
+ let actual = parse(
2886
+ "((n)-[r]->(m) WHERE n.x > 1)",
2887
+ selector_scoped_pattern::<AllowUndirected>,
2888
+ );
2889
+
2890
+ // Verify it returns a single QuantifiedPathPattern
2891
+ assert_eq!(
2892
+ actual.len(),
2893
+ 1,
2894
+ "Expected one pattern, got {}",
2895
+ actual.len()
2896
+ );
2897
+
2898
+ if let ast::Pattern::QuantifiedPath(qp) = &actual[0]
2899
+ {
2900
+ // Verify quantifier is None (key difference from group pattern)
2901
+ assert_eq!(
2902
+ qp.quantifier, None,
2903
+ "Expected quantifier to be None for selector-scoped pattern, got {:?}",
2904
+ qp.quantifier
2905
+ );
2906
+
2907
+ // Verify group_where is set
2908
+ assert!(qp.group_where.is_some(), "Expected group_where to be set");
2909
+
2910
+ // Verify variable is None (not set in selector_scoped_pattern itself)
2911
+ assert_eq!(
2912
+ qp.variable, None,
2913
+ "Expected variable to be None before patterns() closure applies it"
2914
+ );
2915
+
2916
+ // Verify group contains the pattern(s)
2917
+ assert!(!qp.group.is_empty(), "Expected group to contain patterns");
2918
+ }
2919
+ else
2920
+ {
2921
+ panic!("Expected QuantifiedPathPattern, got {:?}", actual[0]);
2922
+ }
2923
+ }
2924
+
2925
+ #[test]
2926
+ fn test_oc_parse_selector_scoped_pattern_with_variable()
2927
+ {
2928
+ // Test parsing selector-scoped pattern with variable: (p = (n)-[r]->(m))
2929
+ // This tests just the selector_scoped_pattern directly
2930
+ let actual = parse(
2931
+ "(p = (n)-[r]->(m))",
2932
+ selector_scoped_pattern::<AllowUndirected>,
2933
+ );
2934
+
2935
+ // Verify it returns a single QuantifiedPathPattern
2936
+ assert_eq!(
2937
+ actual.len(),
2938
+ 1,
2939
+ "Expected one pattern, got {}",
2940
+ actual.len()
2941
+ );
2942
+
2943
+ if let ast::Pattern::QuantifiedPath(qp) = &actual[0]
2944
+ {
2945
+ // Verify variable is set from the p = binding inside the scoped pattern
2946
+ assert!(qp.variable.is_some(), "Expected variable to be set");
2947
+ if let Some(var) = &qp.variable
2948
+ {
2949
+ assert_eq!(
2950
+ var.name(),
2951
+ "p",
2952
+ "Expected variable 'p', got '{}'",
2953
+ var.name()
2954
+ );
2955
+ }
2956
+
2957
+ // Verify quantifier is None (key difference from regular group patterns)
2958
+ assert_eq!(
2959
+ qp.quantifier, None,
2960
+ "Expected quantifier to be None for selector-scoped pattern"
2961
+ );
2962
+ }
2963
+ else
2964
+ {
2965
+ panic!("Expected QuantifiedPathPattern, got {:?}", actual[0]);
2966
+ }
2967
+ }
2968
+
2969
+ #[test]
2970
+ fn test_oc_parse_selector_scoped_pattern_no_where()
2971
+ {
2972
+ // Test parsing selector-scoped pattern without WHERE: ((n)-[r]->(m))
2973
+ let actual = parse("((n)-[r]->(m))", selector_scoped_pattern::<AllowUndirected>);
2974
+
2975
+ if let ast::Pattern::QuantifiedPath(qp) = &actual[0]
2976
+ {
2977
+ // Verify quantifier is None
2978
+ assert_eq!(qp.quantifier, None, "Expected quantifier to be None");
2979
+
2980
+ // Verify group_where is None when no WHERE clause
2981
+ assert_eq!(
2982
+ qp.group_where, None,
2983
+ "Expected group_where to be None when no WHERE clause"
2984
+ );
2985
+ }
2986
+ else
2987
+ {
2988
+ panic!("Expected QuantifiedPathPattern, got {:?}", actual[0]);
2989
+ }
2990
+ }