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
@@ -450,12 +450,13 @@ pub struct OrderByExpression
450
450
 
451
451
  // Values: CreatePatterns
452
452
 
453
- #[derive(Debug)]
453
+ #[derive(Debug, Clone)]
454
454
  pub enum Pattern
455
455
  {
456
456
  Node(NodePattern),
457
457
  Edge(EdgePattern),
458
458
  Path(PathPattern),
459
+ QuantifiedPath(QuantifiedPathPattern),
459
460
  }
460
461
 
461
462
  macro_rules! create_from_pattern {
@@ -470,6 +471,7 @@ pub struct NodePattern
470
471
  pub variable: Option<VariableIdentifier>,
471
472
  pub labels: LabelExpression,
472
473
  pub properties: Option<Expression>,
474
+ pub predicate: Option<Expression>,
473
475
  }
474
476
 
475
477
  create_from_pattern! {NodePattern, Node}
@@ -483,8 +485,7 @@ pub struct EdgePattern
483
485
  pub directivity: graphcore::EdgeDirectivity,
484
486
  pub labels: LabelExpression,
485
487
  pub properties: Option<Expression>,
486
- /// Indicates the range for a recursive match.
487
- pub recursive_range: Option<std::ops::Range<usize>>,
488
+ pub predicate: Option<Expression>,
488
489
  }
489
490
 
490
491
  create_from_pattern! {EdgePattern, Edge}
@@ -498,6 +499,54 @@ pub struct PathPattern
498
499
 
499
500
  create_from_pattern! {PathPattern, Path}
500
501
 
502
+ #[derive(Debug, Clone)]
503
+ pub struct QuantifiedPathPattern
504
+ {
505
+ /// Path variable, if bound via `p = ...`. `None` for a bare quantified
506
+ /// relationship/group with no path variable.
507
+ pub variable: Option<VariableIdentifier>,
508
+ /// The fixed source node anchor outside the repeated group. For
509
+ /// `(a) ((n)-[r]->(m)){1,5} (b)`, this is `a` (the left outer anchor).
510
+ pub source: NodePattern,
511
+ /// The repeated unit. For the single-edge case (legacy `[*min..max]` or
512
+ /// new `{min,max}`/`*`/`+` syntax — the only case Milestone 2 produces),
513
+ /// this is always exactly one `Pattern::Edge`. Multi-element groups
514
+ /// (Milestone 3) populate more than one element here.
515
+ pub group: Vec<Pattern>,
516
+ /// Optional inline `WHERE` scoped to the group (Milestone 3). Always
517
+ /// `None` for anything Milestone 2 produces. Applied per repetition during
518
+ /// group expansion (genuine groups only, never present on wrapper-promoted patterns).
519
+ pub group_where: Option<Expression>,
520
+ /// Optional `WHERE` for the whole assembled path, from a selector-scoped wrapper.
521
+ /// Evaluated once when a candidate reaches `destination`, distinct from
522
+ /// `group_where`'s per-repetition timing. Only populated by wrapper resolution
523
+ /// in `planner::plan_pattern`; genuine groups never have `path_where`.
524
+ pub path_where: Option<Expression>,
525
+ /// `Some(min..max)` for a repeatable group (everything Milestone 2
526
+ /// produces). `None` is reserved for the pre-filter scoping-wrapper use
527
+ /// (Milestone 5, design §1d) — not constructible yet.
528
+ pub quantifier: Option<std::ops::Range<usize>>,
529
+ /// The fixed destination node anchor outside the repeated group. For
530
+ /// `(a) ((n)-[r]->(m)){1,5} (b)`, this is `b` (the right outer anchor).
531
+ pub destination: NodePattern,
532
+ /// Path selector (`SHORTEST`/`ALL SHORTEST`/etc., design's `PathSelector`
533
+ /// below). Always `PathSelector::All` for anything Milestone 2 produces
534
+ /// — no grammar exists yet to populate the other variants.
535
+ pub selector: PathSelector,
536
+ }
537
+
538
+ create_from_pattern! {QuantifiedPathPattern, QuantifiedPath}
539
+
540
+ #[derive(Debug, Clone)]
541
+ pub enum PathSelector
542
+ {
543
+ All,
544
+ Any,
545
+ AllShortest,
546
+ Shortest(Expression),
547
+ ShortestGroups(Expression),
548
+ }
549
+
501
550
  // Label Expression
502
551
  #[derive(Debug, Clone, PartialEq)]
503
552
  pub enum LabelExpression
@@ -27,6 +27,9 @@ pub enum ErrorKind
27
27
  NoSingleRelationshipType,
28
28
  MixedUnionOperators,
29
29
  VariableNotFound,
30
+ CreatingWithPredicate,
31
+ ConflictingQuantifiers,
32
+ EmptyQuantifiedGroup,
30
33
  }
31
34
 
32
35
  pub(crate) fn parse_int_error_to_error_kind(e: std::num::ParseIntError) -> ErrorKind
@@ -157,6 +160,21 @@ impl std::fmt::Display for Error
157
160
  "NoSingleRelationshipType: edge patterns must have exactly one relationship type at {}..{}.",
158
161
  self.span.start, self.span.end
159
162
  ),
163
+ ErrorKind::CreatingWithPredicate => write!(
164
+ f,
165
+ "CreatingWithPredicate: CREATE patterns cannot use an inline WHERE predicate at {}..{}.",
166
+ self.span.start, self.span.end
167
+ ),
168
+ ErrorKind::ConflictingQuantifiers => write!(
169
+ f,
170
+ "ConflictingQuantifiers: cannot specify both a legacy `[*min..max]` range and a `{{min,max}}`/`*`/`+` quantifier on the same relationship at {}..{}.",
171
+ self.span.start, self.span.end
172
+ ),
173
+ ErrorKind::EmptyQuantifiedGroup => write!(
174
+ f,
175
+ "EmptyQuantifiedGroup: a quantified group must contain at least one relationship at {}..{}.",
176
+ self.span.start, self.span.end
177
+ ),
160
178
  ErrorKind::MixedUnionOperators => write!(
161
179
  f,
162
180
  "MixedUnionOperators: cannot mix UNION and UNION ALL in the same query at {}..{}.",
@@ -13,6 +13,8 @@ pub(crate) enum Token
13
13
  And,
14
14
  #[token("all", ignore(case))]
15
15
  All,
16
+ #[token("any", ignore(case))]
17
+ Any,
16
18
  #[token("as", ignore(case))]
17
19
  As,
18
20
  #[token("asc", ignore(case))]
@@ -47,6 +49,10 @@ pub(crate) enum Token
47
49
  False,
48
50
  #[token("graph", ignore(case))]
49
51
  Graph,
52
+ #[token("group", ignore(case))]
53
+ Group,
54
+ #[token("groups", ignore(case))]
55
+ Groups,
50
56
  #[token("if", ignore(case))]
51
57
  If,
52
58
  #[token("index", ignore(case))]
@@ -71,6 +77,8 @@ pub(crate) enum Token
71
77
  Search,
72
78
  #[token("set", ignore(case))]
73
79
  Set,
80
+ #[token("shortest", ignore(case))]
81
+ Shortest,
74
82
  #[token("starts", ignore(case))]
75
83
  Starts,
76
84
  #[token("not", ignore(case))]
@@ -191,6 +199,7 @@ impl Token
191
199
  matches!(
192
200
  self,
193
201
  Token::And
202
+ | Token::Any
194
203
  | Token::As
195
204
  | Token::Asc
196
205
  | Token::By
@@ -205,6 +214,8 @@ impl Token
205
214
  | Token::For
206
215
  | Token::False
207
216
  | Token::Graph
217
+ | Token::Group
218
+ | Token::Groups
208
219
  | Token::If
209
220
  | Token::Index
210
221
  | Token::In
@@ -223,6 +234,7 @@ impl Token
223
234
  | Token::Score
224
235
  | Token::Search
225
236
  | Token::Set
237
+ | Token::Shortest
226
238
  | Token::Skip
227
239
  | Token::True
228
240
  | Token::Union
@@ -435,4 +447,138 @@ mod tests
435
447
  ]
436
448
  );
437
449
  }
450
+
451
+ #[test]
452
+ fn test_oc_lexer_new_keywords()
453
+ {
454
+ // Test 'any' keyword in different cases
455
+ assert_eq!(
456
+ tokenize("any").unwrap(),
457
+ vec![Spanned {
458
+ value: Token::Any,
459
+ span: 0..3
460
+ }]
461
+ );
462
+ assert_eq!(
463
+ tokenize("ANY").unwrap(),
464
+ vec![Spanned {
465
+ value: Token::Any,
466
+ span: 0..3
467
+ }]
468
+ );
469
+ assert_eq!(
470
+ tokenize("Any").unwrap(),
471
+ vec![Spanned {
472
+ value: Token::Any,
473
+ span: 0..3
474
+ }]
475
+ );
476
+
477
+ // Test 'group' keyword in different cases
478
+ assert_eq!(
479
+ tokenize("group").unwrap(),
480
+ vec![Spanned {
481
+ value: Token::Group,
482
+ span: 0..5
483
+ }]
484
+ );
485
+ assert_eq!(
486
+ tokenize("GROUP").unwrap(),
487
+ vec![Spanned {
488
+ value: Token::Group,
489
+ span: 0..5
490
+ }]
491
+ );
492
+ assert_eq!(
493
+ tokenize("Group").unwrap(),
494
+ vec![Spanned {
495
+ value: Token::Group,
496
+ span: 0..5
497
+ }]
498
+ );
499
+
500
+ // Test 'groups' keyword in different cases
501
+ assert_eq!(
502
+ tokenize("groups").unwrap(),
503
+ vec![Spanned {
504
+ value: Token::Groups,
505
+ span: 0..6
506
+ }]
507
+ );
508
+ assert_eq!(
509
+ tokenize("GROUPS").unwrap(),
510
+ vec![Spanned {
511
+ value: Token::Groups,
512
+ span: 0..6
513
+ }]
514
+ );
515
+ assert_eq!(
516
+ tokenize("Groups").unwrap(),
517
+ vec![Spanned {
518
+ value: Token::Groups,
519
+ span: 0..6
520
+ }]
521
+ );
522
+
523
+ // Test 'shortest' keyword in different cases
524
+ assert_eq!(
525
+ tokenize("shortest").unwrap(),
526
+ vec![Spanned {
527
+ value: Token::Shortest,
528
+ span: 0..8
529
+ }]
530
+ );
531
+ assert_eq!(
532
+ tokenize("SHORTEST").unwrap(),
533
+ vec![Spanned {
534
+ value: Token::Shortest,
535
+ span: 0..8
536
+ }]
537
+ );
538
+ assert_eq!(
539
+ tokenize("Shortest").unwrap(),
540
+ vec![Spanned {
541
+ value: Token::Shortest,
542
+ span: 0..8
543
+ }]
544
+ );
545
+ }
546
+
547
+ #[test]
548
+ fn test_oc_lexer_reserved_keywords_compatibility()
549
+ {
550
+ // These keywords are now reserved and cannot be used as identifiers.
551
+ // This test demonstrates the compatibility cost: queries that previously
552
+ // worked with these as variable names will now fail.
553
+
554
+ // Before fix: 'any' would be tokenized as Identifier
555
+ // After fix: 'any' is tokenized as Token::Any (keyword)
556
+ let tokens = tokenize("any").unwrap();
557
+ assert_eq!(
558
+ tokens[0].value,
559
+ Token::Any,
560
+ "Expected 'any' to be reserved keyword, not identifier"
561
+ );
562
+
563
+ let tokens = tokenize("group").unwrap();
564
+ assert_eq!(
565
+ tokens[0].value,
566
+ Token::Group,
567
+ "Expected 'group' to be reserved keyword, not identifier"
568
+ );
569
+
570
+ let tokens = tokenize("groups").unwrap();
571
+ assert_eq!(
572
+ tokens[0].value,
573
+ Token::Groups,
574
+ "Expected 'groups' to be reserved keyword, not identifier"
575
+ );
576
+
577
+ let tokens = tokenize("shortest").unwrap();
578
+ assert_eq!(
579
+ tokens[0].value,
580
+ Token::Shortest,
581
+ "Expected 'shortest' to be reserved keyword, not identifier"
582
+ );
583
+ }
438
584
  }