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
@@ -101,6 +101,17 @@ where
101
101
  assert_eq!(unwrap_nom(input.clone(), parser.parse(input)), exp);
102
102
  }
103
103
 
104
+ fn parse_string<P>(s: &str, mut parser: P, exp: impl Into<String>)
105
+ where
106
+ for<'a> P: nom::Parser<Input<'a>, Output = String, Error = Error>,
107
+ {
108
+ let exp = exp.into();
109
+ let ctx = Context::new(s);
110
+ let tokens = lexer::tokenize(s).expect("tokenization failed");
111
+ let input = Input(&tokens, &ctx);
112
+ assert_eq!(unwrap_nom(input.clone(), parser.parse(input)), exp);
113
+ }
114
+
104
115
  #[test]
105
116
  fn test_oc_parse_context_variable_identity()
106
117
  {
@@ -131,19 +142,19 @@ fn test_oc_parse_parameter_name()
131
142
  #[test]
132
143
  fn test_oc_parse_string_literal_double_quote()
133
144
  {
134
- parse_str(r#""hello world""#, string_literal, "hello world");
145
+ parse_string(r#""hello world""#, string_literal, "hello world");
135
146
  }
136
147
 
137
148
  #[test]
138
149
  fn test_oc_parse_string_literal_single_quote()
139
150
  {
140
- parse_str("'hello world'", string_literal, "hello world");
141
- parse_str("', as#?lßdj '", string_literal, ", as#?lßdj ");
151
+ parse_string("'hello world'", string_literal, "hello world");
152
+ parse_string("', as#?lßdj '", string_literal, ", as#?lßdj ");
142
153
  }
143
154
  #[test]
144
155
  fn test_oc_parse_string_literal_backtick()
145
156
  {
146
- parse_str("`hello`", string_literal, "hello");
157
+ parse_string("`hello`", string_literal, "hello");
147
158
  }
148
159
 
149
160
  #[test]
@@ -307,6 +318,7 @@ fn test_oc_parse_function_call()
307
318
  ast::Expression::Value(ast::Value { value: 1.into() }),
308
319
  ast::Expression::Value(ast::Value { value: 2.into() }),
309
320
  ],
321
+ distinct: false,
310
322
  },
311
323
  );
312
324
  }
@@ -320,6 +332,7 @@ fn test_oc_parse_function_call_all()
320
332
  ast::FunctionCall {
321
333
  name: "func".to_string(),
322
334
  arguments: vec![ast::Expression::Value(ast::Value { value: 0.into() })],
335
+ distinct: false,
323
336
  },
324
337
  );
325
338
  }
@@ -384,6 +397,7 @@ fn test_oc_parse_relational_binary_operator()
384
397
  }
385
398
  .into(),
386
399
  ],
400
+ distinct: false,
387
401
  }
388
402
  .into(),
389
403
  right: ast::FunctionCall {
@@ -397,6 +411,7 @@ fn test_oc_parse_relational_binary_operator()
397
411
  }
398
412
  .into(),
399
413
  ],
414
+ distinct: false,
400
415
  }
401
416
  .into(),
402
417
  },
@@ -406,7 +421,7 @@ fn test_oc_parse_relational_binary_operator()
406
421
  #[test]
407
422
  fn test_oc_parse_node_pattern_simple()
408
423
  {
409
- let actual = parse("(n:Person)", node_pattern);
424
+ let actual = parse("(n:Person)", node_pattern::<AllowUndirected>);
410
425
 
411
426
  let expected = ast::NodePattern {
412
427
  variable: ast::VariableIdentifier {
@@ -416,6 +431,8 @@ fn test_oc_parse_node_pattern_simple()
416
431
  .into(),
417
432
  labels: ast::LabelExpression::String("Person".to_string()),
418
433
  properties: None,
434
+
435
+ predicate: None,
419
436
  };
420
437
  compare_ast(actual, expected);
421
438
  }
@@ -423,7 +440,7 @@ fn test_oc_parse_node_pattern_simple()
423
440
  #[test]
424
441
  fn test_oc_parse_node_pattern_parameters()
425
442
  {
426
- let node = parse("(n {id: 12, name: 'foo'})", node_pattern);
443
+ let node = parse("(n {id: 12, name: 'foo'})", node_pattern::<AllowUndirected>);
427
444
 
428
445
  let expected_node = ast::NodePattern {
429
446
  variable: ast::VariableIdentifier {
@@ -446,11 +463,16 @@ fn test_oc_parse_node_pattern_parameters()
446
463
  ),
447
464
  ],
448
465
  })),
466
+
467
+ predicate: None,
449
468
  };
450
469
 
451
470
  compare_ast(node, expected_node);
452
471
 
453
- 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
+ );
454
476
 
455
477
  let expected_node = ast::NodePattern {
456
478
  variable: ast::VariableIdentifier {
@@ -473,6 +495,8 @@ fn test_oc_parse_node_pattern_parameters()
473
495
  ),
474
496
  ],
475
497
  })),
498
+
499
+ predicate: None,
476
500
  };
477
501
 
478
502
  compare_ast(node, expected_node);
@@ -494,6 +518,7 @@ fn test_oc_parse_edge_patterns()
494
518
  .into(),
495
519
  labels: ast::LabelExpression::None,
496
520
  properties: None,
521
+ predicate: None,
497
522
  },
498
523
  variable: ast::VariableIdentifier {
499
524
  name: "m".to_string(),
@@ -508,11 +533,13 @@ fn test_oc_parse_edge_patterns()
508
533
  .into(),
509
534
  labels: ast::LabelExpression::None,
510
535
  properties: None,
536
+ predicate: None,
511
537
  },
512
538
  directivity: graphcore::EdgeDirectivity::Directed,
513
539
  labels: ast::LabelExpression::None,
514
540
  properties: None,
515
- recursive_range: None,
541
+
542
+ predicate: None,
516
543
  }
517
544
  .into(),
518
545
  ],
@@ -547,7 +574,8 @@ fn test_oc_parse_edge_recursive_patterns()
547
574
  "(n)-[m*1..2]->(p)",
548
575
  patterns::<AllowUndirected>,
549
576
  vec![
550
- ast::EdgePattern {
577
+ ast::QuantifiedPathPattern {
578
+ variable: None,
551
579
  source: ast::NodePattern {
552
580
  variable: ast::VariableIdentifier {
553
581
  name: "n".to_string(),
@@ -556,12 +584,45 @@ fn test_oc_parse_edge_recursive_patterns()
556
584
  .into(),
557
585
  labels: ast::LabelExpression::None,
558
586
  properties: None,
587
+ predicate: None,
559
588
  },
560
- variable: ast::VariableIdentifier {
561
- name: "m".to_string(),
562
- id: 2,
563
- }
564
- .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),
565
626
  destination: ast::NodePattern {
566
627
  variable: ast::VariableIdentifier {
567
628
  name: "p".to_string(),
@@ -570,11 +631,9 @@ fn test_oc_parse_edge_recursive_patterns()
570
631
  .into(),
571
632
  labels: ast::LabelExpression::None,
572
633
  properties: None,
634
+ predicate: None,
573
635
  },
574
- directivity: graphcore::EdgeDirectivity::Directed,
575
- labels: ast::LabelExpression::None,
576
- properties: None,
577
- recursive_range: Some(1..2),
636
+ selector: ast::PathSelector::All,
578
637
  }
579
638
  .into(),
580
639
  ],
@@ -748,7 +807,7 @@ fn test_oc_parse_string_literal()
748
807
  #[test]
749
808
  fn test_oc_parse_node_pattern_with_parameter_properties()
750
809
  {
751
- let node = parse("(n $props)", node_pattern);
810
+ let node = parse("(n $props)", node_pattern::<AllowUndirected>);
752
811
 
753
812
  let expr = node.properties.expect("properties expected");
754
813
 
@@ -774,6 +833,8 @@ fn test_oc_parse_match_statement_without_where()
774
833
  .into(),
775
834
  labels: ast::LabelExpression::None,
776
835
  properties: None,
836
+
837
+ predicate: None,
777
838
  }
778
839
  .into(),
779
840
  ],
@@ -800,6 +861,8 @@ fn test_oc_parse_optional_match_statement_without_where()
800
861
  .into(),
801
862
  labels: ast::LabelExpression::None,
802
863
  properties: None,
864
+
865
+ predicate: None,
803
866
  }
804
867
  .into(),
805
868
  ],
@@ -823,6 +886,7 @@ fn test_oc_parse_match_statement_double_edge()
823
886
  variable: None,
824
887
  labels: ast::LabelExpression::None,
825
888
  properties: None,
889
+ predicate: None,
826
890
  },
827
891
  destination: ast::NodePattern {
828
892
  variable: Some(ast::VariableIdentifier {
@@ -831,12 +895,14 @@ fn test_oc_parse_match_statement_double_edge()
831
895
  }),
832
896
  labels: ast::LabelExpression::None,
833
897
  properties: None,
898
+ predicate: None,
834
899
  },
835
900
  variable: None,
836
901
  directivity: graphcore::EdgeDirectivity::Directed,
837
902
  labels: ast::LabelExpression::None,
838
903
  properties: None,
839
- recursive_range: None,
904
+
905
+ predicate: None,
840
906
  }
841
907
  .into(),
842
908
  ast::EdgePattern {
@@ -847,17 +913,20 @@ fn test_oc_parse_match_statement_double_edge()
847
913
  }),
848
914
  labels: ast::LabelExpression::None,
849
915
  properties: None,
916
+ predicate: None,
850
917
  },
851
918
  destination: ast::NodePattern {
852
919
  variable: None,
853
920
  labels: ast::LabelExpression::None,
854
921
  properties: None,
922
+ predicate: None,
855
923
  },
856
924
  variable: None,
857
925
  directivity: graphcore::EdgeDirectivity::Directed,
858
926
  labels: ast::LabelExpression::None,
859
927
  properties: None,
860
- recursive_range: None,
928
+
929
+ predicate: None,
861
930
  }
862
931
  .into(),
863
932
  ],
@@ -890,6 +959,8 @@ fn test_oc_parse_merge_basic_pattern()
890
959
  }),
891
960
  )],
892
961
  })),
962
+
963
+ predicate: None,
893
964
  }
894
965
  .into(),
895
966
  ],
@@ -914,6 +985,8 @@ fn test_oc_parse_merge_with_on_create_set()
914
985
  }),
915
986
  labels: ast::LabelExpression::String("Person".to_string()),
916
987
  properties: None,
988
+
989
+ predicate: None,
917
990
  }
918
991
  .into(),
919
992
  ],
@@ -945,6 +1018,8 @@ fn test_oc_parse_merge_with_on_match_set()
945
1018
  }),
946
1019
  labels: ast::LabelExpression::String("Person".to_string()),
947
1020
  properties: None,
1021
+
1022
+ predicate: None,
948
1023
  }
949
1024
  .into(),
950
1025
  ],
@@ -979,6 +1054,8 @@ fn test_oc_parse_merge_with_both_on_clauses()
979
1054
  }),
980
1055
  labels: ast::LabelExpression::String("Person".to_string()),
981
1056
  properties: None,
1057
+
1058
+ predicate: None,
982
1059
  }
983
1060
  .into(),
984
1061
  ],
@@ -1020,6 +1097,8 @@ fn test_oc_parse_merge_with_both_on_clauses_reversed()
1020
1097
  }),
1021
1098
  labels: ast::LabelExpression::String("Person".to_string()),
1022
1099
  properties: None,
1100
+
1101
+ predicate: None,
1023
1102
  }
1024
1103
  .into(),
1025
1104
  ],
@@ -1059,6 +1138,7 @@ fn test_oc_parse_merge_with_edge_pattern()
1059
1138
  }),
1060
1139
  labels: ast::LabelExpression::None,
1061
1140
  properties: None,
1141
+ predicate: None,
1062
1142
  },
1063
1143
  destination: ast::NodePattern {
1064
1144
  variable: Some(ast::VariableIdentifier {
@@ -1067,12 +1147,14 @@ fn test_oc_parse_merge_with_edge_pattern()
1067
1147
  }),
1068
1148
  labels: ast::LabelExpression::None,
1069
1149
  properties: None,
1150
+ predicate: None,
1070
1151
  },
1071
1152
  variable: None,
1072
1153
  labels: ast::LabelExpression::String("KNOWS".to_string()),
1073
1154
  properties: None,
1074
1155
  directivity: graphcore::EdgeDirectivity::Directed,
1075
- recursive_range: None,
1156
+
1157
+ predicate: None,
1076
1158
  }
1077
1159
  .into(),
1078
1160
  ],
@@ -1099,6 +1181,8 @@ fn test_oc_parse_merge_statement()
1099
1181
  .into(),
1100
1182
  labels: ast::LabelExpression::None,
1101
1183
  properties: None,
1184
+
1185
+ predicate: None,
1102
1186
  }
1103
1187
  .into(),
1104
1188
  ],
@@ -1113,6 +1197,8 @@ fn test_oc_parse_merge_statement()
1113
1197
  variable: None,
1114
1198
  labels: ast::LabelExpression::None,
1115
1199
  properties: None,
1200
+
1201
+ predicate: None,
1116
1202
  }
1117
1203
  .into(),
1118
1204
  ],
@@ -1127,6 +1213,8 @@ fn test_oc_parse_merge_statement()
1127
1213
  variable: None,
1128
1214
  labels: ast::LabelExpression::String("A".to_string()),
1129
1215
  properties: None,
1216
+
1217
+ predicate: None,
1130
1218
  }
1131
1219
  .into(),
1132
1220
  ],
@@ -1144,6 +1232,8 @@ fn test_oc_parse_merge_statement()
1144
1232
  ast::LabelExpression::String("B".to_string()),
1145
1233
  ]),
1146
1234
  properties: None,
1235
+
1236
+ predicate: None,
1147
1237
  }
1148
1238
  .into(),
1149
1239
  ],
@@ -1164,17 +1254,20 @@ fn test_oc_parse_create_edge_statements()
1164
1254
  variable: None,
1165
1255
  labels: ast::LabelExpression::None,
1166
1256
  properties: None,
1257
+ predicate: None,
1167
1258
  },
1168
1259
  destination: ast::NodePattern {
1169
1260
  variable: None,
1170
1261
  labels: ast::LabelExpression::None,
1171
1262
  properties: None,
1263
+ predicate: None,
1172
1264
  },
1173
1265
  variable: None,
1174
1266
  directivity: graphcore::EdgeDirectivity::Directed,
1175
1267
  labels: ast::LabelExpression::String("R".to_string()),
1176
1268
  properties: None,
1177
- recursive_range: None,
1269
+
1270
+ predicate: None,
1178
1271
  }
1179
1272
  .into(),
1180
1273
  ],
@@ -1193,6 +1286,7 @@ fn test_oc_parse_create_edge_statements()
1193
1286
  }),
1194
1287
  labels: ast::LabelExpression::None,
1195
1288
  properties: None,
1289
+ predicate: None,
1196
1290
  },
1197
1291
  destination: ast::NodePattern {
1198
1292
  variable: Some(ast::VariableIdentifier {
@@ -1201,12 +1295,14 @@ fn test_oc_parse_create_edge_statements()
1201
1295
  }),
1202
1296
  labels: ast::LabelExpression::None,
1203
1297
  properties: None,
1298
+ predicate: None,
1204
1299
  },
1205
1300
  variable: None,
1206
1301
  directivity: graphcore::EdgeDirectivity::Directed,
1207
1302
  labels: ast::LabelExpression::String("FOO".to_string()),
1208
1303
  properties: None,
1209
- recursive_range: None,
1304
+
1305
+ predicate: None,
1210
1306
  }
1211
1307
  .into(),
1212
1308
  ],
@@ -1261,6 +1357,35 @@ fn test_oc_parse_return_n()
1261
1357
  compare_ast(vec![stmt], vec![exppected_stmt]);
1262
1358
  }
1263
1359
 
1360
+ #[test]
1361
+ fn test_oc_parse_return_distinct_n()
1362
+ {
1363
+ let stmt = parse("RETURN DISTINCT n", return_statement);
1364
+
1365
+ let exppected_stmt = ast::Statement::Return(ast::Return {
1366
+ all: false,
1367
+ expressions: vec![ast::NamedExpression {
1368
+ identifier: ast::VariableIdentifier {
1369
+ name: "n".to_string(),
1370
+ id: 0,
1371
+ },
1372
+ expression: ast::Variable {
1373
+ identifier: ast::VariableIdentifier {
1374
+ name: "n".to_string(),
1375
+ id: 0,
1376
+ },
1377
+ }
1378
+ .into(),
1379
+ }],
1380
+ where_expression: None,
1381
+ modifiers: ast::Modifiers {
1382
+ distinct: true,
1383
+ ..Default::default()
1384
+ },
1385
+ });
1386
+ compare_ast(vec![stmt], vec![exppected_stmt]);
1387
+ }
1388
+
1264
1389
  #[test]
1265
1390
  fn test_oc_parse_return_n_m()
1266
1391
  {
@@ -1411,9 +1536,8 @@ fn test_oc_parse_return_n_modifiers()
1411
1536
  }],
1412
1537
  where_expression: None,
1413
1538
  modifiers: ast::Modifiers {
1414
- order_by: None,
1415
1539
  limit: Some(ast::Expression::Value(ast::Value { value: (-1).into() })),
1416
- skip: None,
1540
+ ..Default::default()
1417
1541
  },
1418
1542
  }),
1419
1543
  );
@@ -1529,6 +1653,7 @@ fn test_oc_parse_modifiers()
1529
1653
  }),
1530
1654
  limit: Some(ast::Expression::Value(ast::Value { value: 4.into() })),
1531
1655
  skip: Some(ast::Expression::Value(ast::Value { value: 2.into() })),
1656
+ ..Default::default()
1532
1657
  },
1533
1658
  );
1534
1659
  compare_parse_to_ast(
@@ -1569,15 +1694,15 @@ fn test_oc_parse_modifiers()
1569
1694
  }),
1570
1695
  limit: Some(ast::Expression::Value(ast::Value { value: 4.into() })),
1571
1696
  skip: None,
1697
+ ..Default::default()
1572
1698
  },
1573
1699
  );
1574
1700
  compare_parse_to_ast(
1575
1701
  "LIMIT -1",
1576
1702
  modifiers,
1577
1703
  ast::Modifiers {
1578
- order_by: None,
1579
1704
  limit: Some(ast::Expression::Value(ast::Value { value: (-1).into() })),
1580
- skip: None,
1705
+ ..Default::default()
1581
1706
  },
1582
1707
  );
1583
1708
  }
@@ -1638,7 +1763,7 @@ fn test_oc_parse_with_modifiers()
1638
1763
  ],
1639
1764
  }),
1640
1765
  limit: Some(ast::Expression::Value(ast::Value { value: 4.into() })),
1641
- skip: None,
1766
+ ..Default::default()
1642
1767
  },
1643
1768
  },
1644
1769
  );
@@ -1682,6 +1807,8 @@ fn test_oc_parse_queries_multiple_statements()
1682
1807
  .into(),
1683
1808
  labels: ast::LabelExpression::None,
1684
1809
  properties: None,
1810
+
1811
+ predicate: None,
1685
1812
  }
1686
1813
  .into(),
1687
1814
  ],
@@ -1721,6 +1848,8 @@ fn test_oc_parse_queries_multiple_queries()
1721
1848
  .into(),
1722
1849
  labels: ast::LabelExpression::None,
1723
1850
  properties: None,
1851
+
1852
+ predicate: None,
1724
1853
  }
1725
1854
  .into(),
1726
1855
  ],
@@ -1763,6 +1892,8 @@ fn test_oc_parse_query_union_basic()
1763
1892
  .into(),
1764
1893
  labels: ast::LabelExpression::None,
1765
1894
  properties: None,
1895
+
1896
+ predicate: None,
1766
1897
  }
1767
1898
  .into(),
1768
1899
  ],
@@ -1783,6 +1914,8 @@ fn test_oc_parse_query_union_basic()
1783
1914
  .into(),
1784
1915
  labels: ast::LabelExpression::None,
1785
1916
  properties: None,
1917
+
1918
+ predicate: None,
1786
1919
  }
1787
1920
  .into(),
1788
1921
  ],
@@ -1817,6 +1950,8 @@ fn test_oc_parse_query_union_all_basic()
1817
1950
  .into(),
1818
1951
  labels: ast::LabelExpression::None,
1819
1952
  properties: None,
1953
+
1954
+ predicate: None,
1820
1955
  }
1821
1956
  .into(),
1822
1957
  ],
@@ -1837,6 +1972,8 @@ fn test_oc_parse_query_union_all_basic()
1837
1972
  .into(),
1838
1973
  labels: ast::LabelExpression::None,
1839
1974
  properties: None,
1975
+
1976
+ predicate: None,
1840
1977
  }
1841
1978
  .into(),
1842
1979
  ],
@@ -1871,6 +2008,8 @@ fn test_oc_parse_query_chained_union_basic()
1871
2008
  .into(),
1872
2009
  labels: ast::LabelExpression::None,
1873
2010
  properties: None,
2011
+
2012
+ predicate: None,
1874
2013
  }
1875
2014
  .into(),
1876
2015
  ],
@@ -1891,6 +2030,8 @@ fn test_oc_parse_query_chained_union_basic()
1891
2030
  .into(),
1892
2031
  labels: ast::LabelExpression::None,
1893
2032
  properties: None,
2033
+
2034
+ predicate: None,
1894
2035
  }
1895
2036
  .into(),
1896
2037
  ],
@@ -1911,6 +2052,8 @@ fn test_oc_parse_query_chained_union_basic()
1911
2052
  .into(),
1912
2053
  labels: ast::LabelExpression::None,
1913
2054
  properties: None,
2055
+
2056
+ predicate: None,
1914
2057
  }
1915
2058
  .into(),
1916
2059
  ],
@@ -1943,6 +2086,8 @@ fn test_oc_parse_query_plain()
1943
2086
  .into(),
1944
2087
  labels: ast::LabelExpression::None,
1945
2088
  properties: None,
2089
+
2090
+ predicate: None,
1946
2091
  }
1947
2092
  .into(),
1948
2093
  ],
@@ -2107,6 +2252,19 @@ fn test_oc_call_internal_stats()
2107
2252
  );
2108
2253
  }
2109
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
+
2110
2268
  #[test]
2111
2269
  fn test_oc_parse_match_with_search_basic()
2112
2270
  {
@@ -2127,6 +2285,72 @@ fn test_oc_parse_match_with_search_basic()
2127
2285
  }
2128
2286
  }
2129
2287
 
2288
+ #[test]
2289
+ fn test_oc_parse_starts_with()
2290
+ {
2291
+ compare_parse_to_ast(
2292
+ "s STARTS WITH 'hello'",
2293
+ expression,
2294
+ ast::StartsWith {
2295
+ left: ast::Variable {
2296
+ identifier: ast::VariableIdentifier {
2297
+ name: "s".to_string(),
2298
+ id: 0,
2299
+ },
2300
+ }
2301
+ .into(),
2302
+ right: ast::Value {
2303
+ value: "hello".into(),
2304
+ }
2305
+ .into(),
2306
+ },
2307
+ );
2308
+ }
2309
+
2310
+ #[test]
2311
+ fn test_oc_parse_ends_with()
2312
+ {
2313
+ compare_parse_to_ast(
2314
+ "s ENDS WITH 'world'",
2315
+ expression,
2316
+ ast::EndsWith {
2317
+ left: ast::Variable {
2318
+ identifier: ast::VariableIdentifier {
2319
+ name: "s".to_string(),
2320
+ id: 0,
2321
+ },
2322
+ }
2323
+ .into(),
2324
+ right: ast::Value {
2325
+ value: "world".into(),
2326
+ }
2327
+ .into(),
2328
+ },
2329
+ );
2330
+ }
2331
+
2332
+ #[test]
2333
+ fn test_oc_parse_contains()
2334
+ {
2335
+ compare_parse_to_ast(
2336
+ "s CONTAINS 'test'",
2337
+ expression,
2338
+ ast::Contains {
2339
+ left: ast::Variable {
2340
+ identifier: ast::VariableIdentifier {
2341
+ name: "s".to_string(),
2342
+ id: 0,
2343
+ },
2344
+ }
2345
+ .into(),
2346
+ right: ast::Value {
2347
+ value: "test".into(),
2348
+ }
2349
+ .into(),
2350
+ },
2351
+ );
2352
+ }
2353
+
2130
2354
  #[test]
2131
2355
  fn test_oc_parse_match_with_search_and_score()
2132
2356
  {
@@ -2282,3 +2506,485 @@ fn test_oc_parse_create_vector_index_if_not_exists()
2282
2506
  },
2283
2507
  );
2284
2508
  }
2509
+
2510
+ #[test]
2511
+ fn test_oc_parse_not_wraps_full_comparison()
2512
+ {
2513
+ compare_parse_to_ast(
2514
+ r#"NOT n.name = "Alice""#,
2515
+ expression,
2516
+ ast::LogicalNegation {
2517
+ value: ast::RelationalEqual {
2518
+ left: ast::MemberAccess {
2519
+ left: ast::Variable {
2520
+ identifier: ast::VariableIdentifier {
2521
+ name: "n".to_string(),
2522
+ id: 0,
2523
+ },
2524
+ }
2525
+ .into(),
2526
+ path: vec!["name".to_string()],
2527
+ }
2528
+ .into(),
2529
+ right: ast::Value {
2530
+ value: "Alice".into(),
2531
+ }
2532
+ .into(),
2533
+ }
2534
+ .into(),
2535
+ },
2536
+ );
2537
+ }
2538
+
2539
+ #[test]
2540
+ fn test_oc_parse_not_and_precedence()
2541
+ {
2542
+ compare_parse_to_ast(
2543
+ "NOT a = 1 AND c = 2",
2544
+ expression,
2545
+ ast::LogicalAnd {
2546
+ left: ast::LogicalNegation {
2547
+ value: ast::RelationalEqual {
2548
+ left: ast::Variable {
2549
+ identifier: ast::VariableIdentifier {
2550
+ name: "a".to_string(),
2551
+ id: 0,
2552
+ },
2553
+ }
2554
+ .into(),
2555
+ right: ast::Value { value: 1i64.into() }.into(),
2556
+ }
2557
+ .into(),
2558
+ }
2559
+ .into(),
2560
+ right: ast::RelationalEqual {
2561
+ left: ast::Variable {
2562
+ identifier: ast::VariableIdentifier {
2563
+ name: "c".to_string(),
2564
+ id: 1,
2565
+ },
2566
+ }
2567
+ .into(),
2568
+ right: ast::Value { value: 2i64.into() }.into(),
2569
+ }
2570
+ .into(),
2571
+ },
2572
+ );
2573
+ }
2574
+
2575
+ #[test]
2576
+ fn test_oc_parse_double_not()
2577
+ {
2578
+ compare_parse_to_ast(
2579
+ "NOT NOT a = 1",
2580
+ expression,
2581
+ ast::LogicalNegation {
2582
+ value: ast::LogicalNegation {
2583
+ value: ast::RelationalEqual {
2584
+ left: ast::Variable {
2585
+ identifier: ast::VariableIdentifier {
2586
+ name: "a".to_string(),
2587
+ id: 0,
2588
+ },
2589
+ }
2590
+ .into(),
2591
+ right: ast::Value { value: 1i64.into() }.into(),
2592
+ }
2593
+ .into(),
2594
+ }
2595
+ .into(),
2596
+ },
2597
+ );
2598
+ }
2599
+
2600
+ #[test]
2601
+ fn test_oc_parse_not_true_still_works()
2602
+ {
2603
+ compare_parse_to_ast(
2604
+ "NOT true",
2605
+ expression,
2606
+ ast::LogicalNegation {
2607
+ value: ast::Value { value: true.into() }.into(),
2608
+ },
2609
+ );
2610
+ }
2611
+
2612
+ #[test]
2613
+ fn test_oc_parse_is_not_null_unaffected()
2614
+ {
2615
+ compare_parse_to_ast(
2616
+ "n.age IS NOT NULL",
2617
+ expression,
2618
+ ast::IsNotNull {
2619
+ value: ast::MemberAccess {
2620
+ left: ast::Variable {
2621
+ identifier: ast::VariableIdentifier {
2622
+ name: "n".to_string(),
2623
+ id: 0,
2624
+ },
2625
+ }
2626
+ .into(),
2627
+ path: vec!["age".to_string()],
2628
+ }
2629
+ .into(),
2630
+ },
2631
+ );
2632
+ }
2633
+
2634
+ #[test]
2635
+ fn test_oc_parse_not_in_still_works()
2636
+ {
2637
+ compare_parse_to_ast(
2638
+ "x NOT IN [1, 2]",
2639
+ expression,
2640
+ ast::LogicalNegation {
2641
+ value: ast::RelationalIn {
2642
+ left: ast::Variable {
2643
+ identifier: ast::VariableIdentifier {
2644
+ name: "x".to_string(),
2645
+ id: 0,
2646
+ },
2647
+ }
2648
+ .into(),
2649
+ right: ast::Array {
2650
+ array: vec![
2651
+ ast::Value { value: 1i64.into() }.into(),
2652
+ ast::Value { value: 2i64.into() }.into(),
2653
+ ],
2654
+ }
2655
+ .into(),
2656
+ }
2657
+ .into(),
2658
+ },
2659
+ );
2660
+ }
2661
+
2662
+ #[test]
2663
+ fn test_oc_parse_count_distinct()
2664
+ {
2665
+ compare_parse_to_ast(
2666
+ "count(DISTINCT x)",
2667
+ expression,
2668
+ ast::FunctionCall {
2669
+ name: "count".to_string(),
2670
+ arguments: vec![
2671
+ ast::Variable {
2672
+ identifier: ast::VariableIdentifier {
2673
+ name: "x".to_string(),
2674
+ id: 0,
2675
+ },
2676
+ }
2677
+ .into(),
2678
+ ],
2679
+ distinct: true,
2680
+ },
2681
+ );
2682
+ }
2683
+
2684
+ #[test]
2685
+ fn test_oc_parse_count_without_distinct_defaults_false()
2686
+ {
2687
+ compare_parse_to_ast(
2688
+ "count(x)",
2689
+ expression,
2690
+ ast::FunctionCall {
2691
+ name: "count".to_string(),
2692
+ arguments: vec![
2693
+ ast::Variable {
2694
+ identifier: ast::VariableIdentifier {
2695
+ name: "x".to_string(),
2696
+ id: 0,
2697
+ },
2698
+ }
2699
+ .into(),
2700
+ ],
2701
+ distinct: false,
2702
+ },
2703
+ );
2704
+ }
2705
+
2706
+ #[test]
2707
+ fn test_oc_parse_sum_distinct()
2708
+ {
2709
+ compare_parse_to_ast(
2710
+ "sum(DISTINCT n.age)",
2711
+ expression,
2712
+ ast::FunctionCall {
2713
+ name: "sum".to_string(),
2714
+ arguments: vec![
2715
+ ast::MemberAccess {
2716
+ left: ast::Variable {
2717
+ identifier: ast::VariableIdentifier {
2718
+ name: "n".to_string(),
2719
+ id: 0,
2720
+ },
2721
+ }
2722
+ .into(),
2723
+ path: vec!["age".to_string()],
2724
+ }
2725
+ .into(),
2726
+ ],
2727
+ distinct: true,
2728
+ },
2729
+ );
2730
+ }
2731
+
2732
+ #[test]
2733
+ fn test_oc_parse_function_call_distinct_with_multiple_args()
2734
+ {
2735
+ compare_parse_to_ast(
2736
+ "some_func(DISTINCT a, b)",
2737
+ expression,
2738
+ ast::FunctionCall {
2739
+ name: "some_func".to_string(),
2740
+ arguments: vec![
2741
+ ast::Variable {
2742
+ identifier: ast::VariableIdentifier {
2743
+ name: "a".to_string(),
2744
+ id: 0,
2745
+ },
2746
+ }
2747
+ .into(),
2748
+ ast::Variable {
2749
+ identifier: ast::VariableIdentifier {
2750
+ name: "b".to_string(),
2751
+ id: 1,
2752
+ },
2753
+ }
2754
+ .into(),
2755
+ ],
2756
+ distinct: true,
2757
+ },
2758
+ );
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
+ }