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
@@ -3,13 +3,13 @@ use gqlparser::oc::ast;
3
3
  use crate::{
4
4
  compiler::{expression_analyser::ExpressionType, variables_manager::VariablesManager},
5
5
  interpreter::instructions::{CreateActionVariable, VariablesSizes},
6
- planner::{LogicalPlan, LogicalPlans, ProjectionKind},
6
+ planner::{self, LogicalPlan, LogicalPlans, ProjectionKind},
7
7
  prelude::*,
8
8
  };
9
9
 
10
10
  use interpreter::instructions::{
11
- self, Block, BlockMatch, CreateAction, Instruction, Instructions, Modifiers, RWAggregation,
12
- RWExpression,
11
+ self, Block, BlockMatch, CreateAction, Instruction, Instructions, Modifiers, PathStrategy,
12
+ RWAggregation, RWExpression, StopCondition,
13
13
  };
14
14
 
15
15
  pub(crate) mod expression_analyser;
@@ -49,6 +49,59 @@ fn is_scan_plan(plan: &LogicalPlan) -> bool
49
49
  }
50
50
  }
51
51
 
52
+ fn collect_pattern_variables(patterns: &[ast::Pattern]) -> Vec<ast::VariableIdentifier>
53
+ {
54
+ let mut variables = Vec::new();
55
+ for pattern in patterns
56
+ {
57
+ match pattern
58
+ {
59
+ ast::Pattern::Node(n) => variables.extend(n.variable.clone()),
60
+ ast::Pattern::Edge(e) =>
61
+ {
62
+ variables.extend(e.source.variable.clone());
63
+ variables.extend(e.variable.clone());
64
+ variables.extend(e.destination.variable.clone());
65
+ }
66
+ ast::Pattern::Path(p) => variables.push(p.variable.clone()),
67
+ ast::Pattern::QuantifiedPath(qp) =>
68
+ {
69
+ variables.extend(qp.variable.clone());
70
+ variables.extend(collect_pattern_variables(&qp.group));
71
+ }
72
+ }
73
+ }
74
+ variables
75
+ }
76
+
77
+ /// If this QuantifiedPathPattern is a "plain" single-edge quantifier -
78
+ /// group.len() == 1, and the group's own edge already has
79
+ /// qp.source/qp.destination as its source/destination (Epic 2's
80
+ /// original construction) - returns that edge. Returns None for
81
+ /// everything else: multi-element groups, or explicit group syntax
82
+ /// where the group's own nodes differ from the outer anchors.
83
+ pub(super) fn as_plain_quantifier(qp: &ast::QuantifiedPathPattern) -> Option<&ast::EdgePattern>
84
+ {
85
+ if qp.group.len() != 1
86
+ {
87
+ return None;
88
+ }
89
+ let ast::Pattern::Edge(inner_edge) = &qp.group[0]
90
+ else
91
+ {
92
+ return None;
93
+ };
94
+ if inner_edge.source.variable == qp.source.variable
95
+ && inner_edge.destination.variable == qp.destination.variable
96
+ {
97
+ Some(inner_edge)
98
+ }
99
+ else
100
+ {
101
+ None
102
+ }
103
+ }
104
+
52
105
  struct Compiler
53
106
  {
54
107
  function_manager: functions::Manager,
@@ -74,6 +127,35 @@ impl Compiler
74
127
  col_id + self.variables_manager.variables_count()
75
128
  }
76
129
 
130
+ fn compile_path_strategy(&mut self, strategy: &planner::PathStrategy) -> Result<PathStrategy>
131
+ {
132
+ match strategy
133
+ {
134
+ planner::PathStrategy::Dfs => Ok(PathStrategy::Dfs),
135
+ planner::PathStrategy::Bfs { stop_condition } =>
136
+ {
137
+ let compiled_stop_condition = match stop_condition
138
+ {
139
+ planner::StopCondition::Shortest(expr) =>
140
+ {
141
+ let mut instructions = Instructions::new();
142
+ self.compile_expression(expr, &mut instructions, &mut None)?;
143
+ StopCondition::Shortest(instructions)
144
+ }
145
+ planner::StopCondition::ShortestGroups(expr) =>
146
+ {
147
+ let mut instructions = Instructions::new();
148
+ self.compile_expression(expr, &mut instructions, &mut None)?;
149
+ StopCondition::ShortestGroups(instructions)
150
+ }
151
+ };
152
+ Ok(PathStrategy::Bfs {
153
+ stop_condition: compiled_stop_condition,
154
+ })
155
+ }
156
+ }
157
+ }
158
+
77
159
  fn compile_expression(
78
160
  &mut self,
79
161
  expression: &gqlparser::oc::ast::Expression,
@@ -441,11 +523,6 @@ impl Compiler
441
523
  .variables_manager
442
524
  .mark_variables_as_set(&edge.variable)?;
443
525
  self.compile_optional_expression(&edge.properties, instructions)?;
444
- // Reject variable-length relationships in CREATE statements
445
- if edge.recursive_range.is_some()
446
- {
447
- return Err(CompileTimeError::CreatingVarLength.into());
448
- }
449
526
  if !edge.labels.is_string()
450
527
  {
451
528
  Err(CompileTimeError::NoSingleRelationshipType {
@@ -561,7 +638,7 @@ impl Compiler
561
638
 
562
639
  fn compile_create_patterns(
563
640
  &mut self,
564
- patterns: impl IntoIterator<Item = gqlparser::oc::ast::Pattern>,
641
+ patterns: impl IntoIterator<Item = planner::PlannedPattern>,
565
642
  ) -> Result<Block>
566
643
  {
567
644
  let actions = patterns.into_iter().map(|c| {
@@ -569,15 +646,15 @@ impl Compiler
569
646
  let mut variables = Vec::<CreateActionVariable>::new();
570
647
  match c
571
648
  {
572
- gqlparser::oc::ast::Pattern::Node(node) =>
649
+ planner::PlannedPattern::Node(node) =>
573
650
  {
574
651
  self.compile_create_node(node, &mut instructions, &mut variables)?;
575
652
  }
576
- gqlparser::oc::ast::Pattern::Edge(edge) =>
653
+ planner::PlannedPattern::Edge(edge) =>
577
654
  {
578
655
  self.compile_create_edge(edge, &mut instructions, &mut variables, None)?;
579
656
  }
580
- gqlparser::oc::ast::Pattern::Path(path) =>
657
+ planner::PlannedPattern::Path(path) =>
581
658
  {
582
659
  self
583
660
  .variables_manager
@@ -589,6 +666,10 @@ impl Compiler
589
666
  Some(path.variable),
590
667
  )?;
591
668
  }
669
+ planner::PlannedPattern::QuantifiedPath { .. } =>
670
+ {
671
+ return Err(CompileTimeError::CreatingVarLength.into());
672
+ }
592
673
  }
593
674
  Ok(CreateAction {
594
675
  instructions,
@@ -635,6 +716,26 @@ impl Compiler
635
716
  filter.push(Instruction::AndBinaryOperator);
636
717
  filter.push(Instruction::Swap);
637
718
  }
719
+ if let Some(predicate) = &node.predicate
720
+ {
721
+ self.compile_expression(predicate, filter, &mut None)?;
722
+ // When there are no labels, we use InverseRot3 to combine the predicate result
723
+ // with the passes_so_far/candidate stack (same as the label loop does).
724
+ // When there are labels, the outer code already uses Rot3, so we only
725
+ // apply these stack operations if there were no labels.
726
+ if node.labels.is_all_inclusive()
727
+ {
728
+ filter.push(Instruction::InverseRot3);
729
+ filter.push(Instruction::AndBinaryOperator);
730
+ filter.push(Instruction::Swap);
731
+ }
732
+ else
733
+ {
734
+ filter.push(Instruction::Rot3);
735
+ filter.push(Instruction::AndBinaryOperator);
736
+ filter.push(Instruction::Swap);
737
+ }
738
+ }
638
739
  instructions.push(Instruction::CreateNodeQuery { labels });
639
740
  Ok(())
640
741
  }
@@ -645,6 +746,7 @@ impl Compiler
645
746
  edge: &gqlparser::oc::ast::EdgePattern,
646
747
  single_match: bool,
647
748
  previous_edges: &mut Vec<usize>,
749
+ recursive_range: Option<std::ops::Range<usize>>,
648
750
  ) -> Result<BlockMatch>
649
751
  {
650
752
  let mut instructions = Instructions::new();
@@ -697,6 +799,15 @@ impl Compiler
697
799
  }
698
800
  if self.variables_manager.is_set_variable(&edge.variable)?
699
801
  {
802
+ // Predicates on reused edge variables are not supported.
803
+ // This intentionally diverges from the silent-ignore behavior for
804
+ // `properties` on reused variables: predicates represent load-bearing
805
+ // user intent (a filter condition), whereas redundant properties on a
806
+ // reused variable are more likely user error that's safe to ignore.
807
+ if edge.predicate.is_some()
808
+ {
809
+ return Err(CompileTimeError::InvalidArgumentType.into());
810
+ }
700
811
  instructions.push(Instruction::GetVariable {
701
812
  col_id: self
702
813
  .variables_manager
@@ -723,6 +834,23 @@ impl Compiler
723
834
  filter.push(Instruction::AndBinaryOperator);
724
835
  filter.push(Instruction::Swap);
725
836
  }
837
+ if let Some(predicate) = &edge.predicate
838
+ {
839
+ self.compile_expression(predicate, &mut filter, &mut None)?;
840
+ // Same logic as compile_match_node: use InverseRot3 when there are no labels
841
+ if edge.labels.is_all_inclusive()
842
+ {
843
+ filter.push(Instruction::InverseRot3);
844
+ filter.push(Instruction::AndBinaryOperator);
845
+ filter.push(Instruction::Swap);
846
+ }
847
+ else
848
+ {
849
+ filter.push(Instruction::Rot3);
850
+ filter.push(Instruction::AndBinaryOperator);
851
+ filter.push(Instruction::Swap);
852
+ }
853
+ }
726
854
  instructions.push(Instruction::CreateEdgeQuery { labels });
727
855
  }
728
856
 
@@ -780,7 +908,281 @@ impl Compiler
780
908
  .get_variable_index_option(&path_variable)?,
781
909
  filter,
782
910
  directivity: edge.directivity,
783
- recursive_range: edge.recursive_range.clone(),
911
+ recursive_range,
912
+ })
913
+ }
914
+
915
+ fn compile_quantified_path(
916
+ &mut self,
917
+ qp: ast::QuantifiedPathPattern,
918
+ strategy: &planner::PathStrategy,
919
+ ) -> Result<BlockMatch>
920
+ {
921
+ let mut source_instructions = Instructions::new();
922
+ let mut source_filter = Instructions::new();
923
+ let source_variable = qp.source.variable.clone();
924
+ if !self
925
+ .variables_manager
926
+ .is_set_variable(&qp.source.variable)?
927
+ {
928
+ self.compile_match_node(
929
+ &qp.source,
930
+ &mut source_instructions,
931
+ &mut source_filter,
932
+ None,
933
+ )?;
934
+ }
935
+
936
+ let destination_variable = qp.destination.variable.clone();
937
+ let mut destination_filter = Instructions::new();
938
+ let is_dest_set = self
939
+ .variables_manager
940
+ .is_set_variable(&qp.destination.variable)?;
941
+ if !is_dest_set
942
+ {
943
+ // Do NOT call compile_match_node here. Its filter output assumes it's being
944
+ // appended into an already-running stack-based accumulator (with Duplicate/Rot3/Swap
945
+ // instructions). check_destination_filter always starts from a fresh, empty stack.
946
+ // This is a fundamental contract mismatch. Instead, build destination_filter
947
+ // directly as a standalone, empty-stack-starting boolean check.
948
+
949
+ let dest_col = match &qp.destination.variable
950
+ {
951
+ Some(var_id) => self.variables_manager.get_variable_index(var_id)?,
952
+ None =>
953
+ {
954
+ return Err(
955
+ error::InternalError::Unreachable {
956
+ context: "compile_quantified_path: destination.variable should always be Some",
957
+ }
958
+ .into(),
959
+ );
960
+ }
961
+ };
962
+
963
+ let mut checks: Vec<Instructions> = Vec::new();
964
+
965
+ // Build label check
966
+ if !matches!(qp.destination.labels, ast::LabelExpression::None)
967
+ {
968
+ let mut label_check = Instructions::new();
969
+ label_check.push(Instruction::GetVariable { col_id: dest_col });
970
+ let has_label_function = self
971
+ .function_manager
972
+ .get_function::<CompileTimeError>("has_label")?;
973
+ Self::compile_filter_labels(
974
+ &mut label_check,
975
+ &qp.destination.labels,
976
+ &has_label_function,
977
+ )?;
978
+ checks.push(label_check);
979
+ }
980
+
981
+ // Build property checks
982
+ if let Some(properties) = &qp.destination.properties
983
+ {
984
+ if let ast::Expression::Map(map) = properties
985
+ {
986
+ for (key, value_expr) in &map.map
987
+ {
988
+ let mut prop_check = Instructions::new();
989
+ prop_check.push(Instruction::GetVariable { col_id: dest_col });
990
+ prop_check.push(Instruction::MemberAccess {
991
+ path: vec![key.clone()],
992
+ });
993
+ self.compile_expression(value_expr, &mut prop_check, &mut None)?;
994
+ prop_check.push(Instruction::EqualBinaryOperator);
995
+ checks.push(prop_check);
996
+ }
997
+ }
998
+ else
999
+ {
1000
+ // Non-Map properties expressions (parameters, variables, etc.) not yet supported
1001
+ return Err(error::InternalError::Unreachable {
1002
+ context: "compile_quantified_path: destination properties as non-Map expression not yet supported for destination_filter",
1003
+ }
1004
+ .into());
1005
+ }
1006
+ }
1007
+
1008
+ // Build predicate (WHERE) check
1009
+ if let Some(predicate) = &qp.destination.predicate
1010
+ {
1011
+ let mut pred_check = Instructions::new();
1012
+ self.compile_expression(predicate, &mut pred_check, &mut None)?;
1013
+ checks.push(pred_check);
1014
+ }
1015
+
1016
+ // Combine all checks with AND
1017
+ for (i, check) in checks.into_iter().enumerate()
1018
+ {
1019
+ destination_filter.extend(check);
1020
+ if i > 0
1021
+ {
1022
+ destination_filter.push(Instruction::AndBinaryOperator);
1023
+ }
1024
+ }
1025
+ }
1026
+
1027
+ // Mark the group's own entry point as already-bound BEFORE compiling
1028
+ // group_instructions - this tells compile_match_edge to generate GetVariable
1029
+ // instructions instead of unconstrained CreateNodeQuery, so the source node
1030
+ // will be constrained to whatever value is in that column at runtime.
1031
+ let group_entry_variable_identifier = qp
1032
+ .group
1033
+ .first()
1034
+ .and_then(|p| {
1035
+ match p {
1036
+ ast::Pattern::Node(n) => n.variable.clone(),
1037
+ ast::Pattern::Edge(e) => e.source.variable.clone(),
1038
+ _ => None,
1039
+ }
1040
+ })
1041
+ .ok_or_else(|| error::InternalError::Unreachable {
1042
+ context: "compile_quantified_path: group should start with a node or edge (guaranteed by parser)",
1043
+ })?;
1044
+ self
1045
+ .variables_manager
1046
+ .mark_variables_as_set(&Some(group_entry_variable_identifier.clone()))?;
1047
+ let group_entry_variable = self
1048
+ .variables_manager
1049
+ .get_variable_index(&group_entry_variable_identifier)?;
1050
+
1051
+ // For bare quantifiers (where the group's terminal node is the same as the
1052
+ // outer destination), create a modified group with unconstrained destination
1053
+ // nodes. This prevents compile_match_edge from baking qp.destination's
1054
+ // constraints into every per-hop edge query.
1055
+ let group_terminal_variable_identifier = qp
1056
+ .group
1057
+ .iter()
1058
+ .rev()
1059
+ .find_map(|p| {
1060
+ if let ast::Pattern::Edge(e) = p {
1061
+ e.destination.variable.clone()
1062
+ } else {
1063
+ None
1064
+ }
1065
+ })
1066
+ .ok_or_else(|| error::InternalError::Unreachable {
1067
+ context: "compile_quantified_path: group should have at least one edge (guaranteed by parser)",
1068
+ })?;
1069
+
1070
+ let modified_group = if Some(&group_terminal_variable_identifier)
1071
+ == qp.destination.variable.as_ref()
1072
+ && (qp.destination.labels != ast::LabelExpression::None
1073
+ || qp.destination.properties.is_some()
1074
+ || qp.destination.predicate.is_some())
1075
+ {
1076
+ // Create a copy of the group with unconstrained destination
1077
+ qp.group
1078
+ .iter()
1079
+ .map(|p| {
1080
+ if let ast::Pattern::Edge(e) = p
1081
+ {
1082
+ let mut modified_edge = e.clone();
1083
+ // Strip constraints from destination while keeping the variable name
1084
+ modified_edge.destination = ast::NodePattern {
1085
+ variable: modified_edge.destination.variable.clone(),
1086
+ labels: ast::LabelExpression::None,
1087
+ properties: None,
1088
+ predicate: None,
1089
+ };
1090
+ ast::Pattern::Edge(modified_edge)
1091
+ }
1092
+ else
1093
+ {
1094
+ p.clone()
1095
+ }
1096
+ })
1097
+ .collect()
1098
+ }
1099
+ else
1100
+ {
1101
+ qp.group.clone()
1102
+ };
1103
+
1104
+ let group_variable_identifiers = collect_pattern_variables(&modified_group);
1105
+ let planned_group = planner::plan_patterns(modified_group.clone());
1106
+ let group_instructions = self.compile_pattern_blocks(&planned_group)?;
1107
+ let group_variables = group_variable_identifiers
1108
+ .iter()
1109
+ .map(|v| self.variables_manager.get_variable_index(v))
1110
+ .collect::<Result<Vec<_>>>()?;
1111
+
1112
+ // Find the terminal variable: the destination of the last edge in the group
1113
+ let group_terminal_variable = self
1114
+ .variables_manager
1115
+ .get_variable_index(&group_terminal_variable_identifier)?;
1116
+
1117
+ let group_where_filter = match &qp.group_where
1118
+ {
1119
+ Some(expr) =>
1120
+ {
1121
+ let mut instructions = Instructions::new();
1122
+ self.compile_expression(expr, &mut instructions, &mut None)?;
1123
+ Some(instructions)
1124
+ }
1125
+ None => None,
1126
+ };
1127
+
1128
+ let path_filter = match &qp.path_where
1129
+ {
1130
+ Some(expr) =>
1131
+ {
1132
+ let mut instructions = Instructions::new();
1133
+ self.compile_expression(expr, &mut instructions, &mut None)?;
1134
+ Some(instructions)
1135
+ }
1136
+ None => None,
1137
+ };
1138
+
1139
+ let path_variable_opt = qp.variable.clone();
1140
+ let path_variable = path_variable_opt
1141
+ .as_ref()
1142
+ .map(|v| self.variables_manager.get_variable_index(v))
1143
+ .transpose()?;
1144
+
1145
+ // Mark all pattern variables as set (same as compile_match_edge does)
1146
+ self
1147
+ .variables_manager
1148
+ .mark_variables_as_set(&source_variable)?;
1149
+ self
1150
+ .variables_manager
1151
+ .mark_variables_as_set(&destination_variable)?;
1152
+ self
1153
+ .variables_manager
1154
+ .mark_variables_as_set(&path_variable_opt)?;
1155
+ for var in &group_variable_identifiers
1156
+ {
1157
+ self
1158
+ .variables_manager
1159
+ .mark_variables_as_set(&Some(var.clone()))?;
1160
+ }
1161
+
1162
+ let compiled_strategy = self.compile_path_strategy(strategy)?;
1163
+
1164
+ Ok(BlockMatch::MatchQuantifiedPath {
1165
+ source_instructions,
1166
+ source_variable: source_variable
1167
+ .map(|v| self.variables_manager.get_variable_index(&v))
1168
+ .transpose()?,
1169
+ source_filter,
1170
+ destination_variable: destination_variable
1171
+ .map(|v| self.variables_manager.get_variable_index(&v))
1172
+ .transpose()?,
1173
+ destination_filter,
1174
+ path_variable,
1175
+ quantifier: qp.quantifier.expect(
1176
+ "compile_quantified_path called with quantifier: None — the pre-filter \
1177
+ scoping-wrapper use (design §1d) isn't reachable until Milestone 5",
1178
+ ),
1179
+ group_instructions,
1180
+ group_variables,
1181
+ group_terminal_variable,
1182
+ group_entry_variable,
1183
+ group_where_filter,
1184
+ path_filter,
1185
+ strategy: compiled_strategy,
784
1186
  })
785
1187
  }
786
1188
 
@@ -869,45 +1271,77 @@ impl Compiler
869
1271
  })
870
1272
  }
871
1273
 
872
- fn compile_match_patterns(
1274
+ fn compile_pattern_blocks(
873
1275
  &mut self,
874
- patterns: &[gqlparser::oc::ast::Pattern],
875
- where_expression: &Option<gqlparser::oc::ast::Expression>,
876
- optional: bool,
877
- ) -> Result<Block>
1276
+ patterns: &[planner::PlannedPattern],
1277
+ ) -> Result<Vec<BlockMatch>>
878
1278
  {
879
1279
  let is_single_match = patterns.len() == 1;
880
1280
  let mut edge_variables = vec![];
881
- let blocks = patterns.iter().map(|c| match c
882
- {
883
- gqlparser::oc::ast::Pattern::Node(node) =>
1281
+ patterns
1282
+ .iter()
1283
+ .map(|c| match c
884
1284
  {
885
- let mut instructions = Instructions::new();
886
- let mut filter = Instructions::new();
887
- self.compile_match_node(node, &mut instructions, &mut filter, None)?;
888
- self
889
- .variables_manager
890
- .mark_variables_as_set(&node.variable)?;
891
- Ok(BlockMatch::MatchNode {
892
- instructions,
893
- variable: self
1285
+ planner::PlannedPattern::Node(node) =>
1286
+ {
1287
+ let mut instructions = Instructions::new();
1288
+ let mut filter = Instructions::new();
1289
+ self.compile_match_node(node, &mut instructions, &mut filter, None)?;
1290
+ self
894
1291
  .variables_manager
895
- .get_variable_index_option(&node.variable)?,
896
- filter,
897
- })
898
- }
899
- gqlparser::oc::ast::Pattern::Edge(edge) =>
900
- {
901
- self.compile_match_edge(None, edge, is_single_match, &mut edge_variables)
902
- }
903
- gqlparser::oc::ast::Pattern::Path(path) => self.compile_match_edge(
904
- Some(path.variable.to_owned()),
905
- &path.edge,
906
- is_single_match,
907
- &mut edge_variables,
908
- ),
909
- });
910
- let blocks = blocks.collect::<Result<_>>()?;
1292
+ .mark_variables_as_set(&node.variable)?;
1293
+ Ok(BlockMatch::MatchNode {
1294
+ instructions,
1295
+ variable: self
1296
+ .variables_manager
1297
+ .get_variable_index_option(&node.variable)?,
1298
+ filter,
1299
+ })
1300
+ }
1301
+ planner::PlannedPattern::Edge(edge) =>
1302
+ {
1303
+ self.compile_match_edge(None, edge, is_single_match, &mut edge_variables, None)
1304
+ }
1305
+ planner::PlannedPattern::Path(path) => self.compile_match_edge(
1306
+ Some(path.variable.to_owned()),
1307
+ &path.edge,
1308
+ is_single_match,
1309
+ &mut edge_variables,
1310
+ None,
1311
+ ),
1312
+ planner::PlannedPattern::QuantifiedPath {
1313
+ pattern: qp,
1314
+ strategy,
1315
+ } =>
1316
+ {
1317
+ if let Some(edge) = as_plain_quantifier(qp)
1318
+ && matches!(strategy, planner::PathStrategy::Dfs)
1319
+ {
1320
+ self.compile_match_edge(
1321
+ qp.variable.clone(),
1322
+ edge,
1323
+ is_single_match,
1324
+ &mut edge_variables,
1325
+ qp.quantifier.clone(),
1326
+ )
1327
+ }
1328
+ else
1329
+ {
1330
+ self.compile_quantified_path(qp.clone(), strategy)
1331
+ }
1332
+ }
1333
+ })
1334
+ .collect::<Result<_>>()
1335
+ }
1336
+
1337
+ fn compile_match_patterns(
1338
+ &mut self,
1339
+ patterns: &[planner::PlannedPattern],
1340
+ where_expression: &Option<gqlparser::oc::ast::Expression>,
1341
+ optional: bool,
1342
+ ) -> Result<Block>
1343
+ {
1344
+ let blocks = self.compile_pattern_blocks(patterns)?;
911
1345
  let mut filter = Instructions::new();
912
1346
  if let Some(where_expression) = where_expression
913
1347
  {
@@ -1005,7 +1439,7 @@ impl Compiler
1005
1439
 
1006
1440
  fn unpack_scan_plan(
1007
1441
  logical_plan: LogicalPlan,
1008
- ) -> Result<(Vec<ast::Pattern>, Option<ast::Expression>, bool)>
1442
+ ) -> Result<(Vec<planner::PlannedPattern>, Option<ast::Expression>, bool)>
1009
1443
  {
1010
1444
  match logical_plan
1011
1445
  {
@@ -122,6 +122,18 @@ where
122
122
  text: query.substring(e.span().start, e.span().end).to_string(),
123
123
  span: e.span().clone(),
124
124
  },
125
+ ErrorKind::CreatingWithPredicate => error::CompileTimeError::CreatingWithPredicate {
126
+ text: query.substring(e.span().start, e.span().end).to_string(),
127
+ span: e.span().clone(),
128
+ },
129
+ ErrorKind::ConflictingQuantifiers => error::CompileTimeError::ConflictingQuantifiers {
130
+ text: query.substring(e.span().start, e.span().end).to_string(),
131
+ span: e.span().clone(),
132
+ },
133
+ ErrorKind::EmptyQuantifiedGroup => error::CompileTimeError::EmptyQuantifiedGroup {
134
+ text: query.substring(e.span().start, e.span().end).to_string(),
135
+ span: e.span().clone(),
136
+ },
125
137
  _ => error::CompileTimeError::ParseError {
126
138
  error_kind: e.error_kind().clone(),
127
139
  text: query.substring(e.span().start, e.span().end).to_string(),
@@ -5,5 +5,5 @@ pub(crate) const SHOW_EVALUATOR_STATE: bool = false;
5
5
  pub(crate) const GQLITE_VERSION: crate::utils::Version = crate::utils::Version {
6
6
  major: 1,
7
7
  minor: 8,
8
- patch: 1,
8
+ patch: 2,
9
9
  };
@@ -71,6 +71,30 @@ pub enum CompileTimeError
71
71
  text: String,
72
72
  span: std::ops::Range<usize>,
73
73
  },
74
+ #[error(
75
+ "CreatingWithPredicate: CREATE patterns cannot use an inline WHERE predicate at: '{text}' at {span:?}."
76
+ )]
77
+ CreatingWithPredicate
78
+ {
79
+ text: String,
80
+ span: std::ops::Range<usize>,
81
+ },
82
+ #[error(
83
+ "ConflictingQuantifiers: cannot specify both a legacy `[*min..max]` range and a `{{min,max}}`/`*`/`+` quantifier on the same relationship at: '{text}' at {span:?}."
84
+ )]
85
+ ConflictingQuantifiers
86
+ {
87
+ text: String,
88
+ span: std::ops::Range<usize>,
89
+ },
90
+ #[error(
91
+ "EmptyQuantifiedGroup: a quantified group must contain at least one relationship at: '{text}' at {span:?}."
92
+ )]
93
+ EmptyQuantifiedGroup
94
+ {
95
+ text: String,
96
+ span: std::ops::Range<usize>,
97
+ },
74
98
  #[error("NotComparable: values are not comparable.")]
75
99
  NotComparable,
76
100
  #[error("UnknownFunction: {name}.")]