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.
- checksums.yaml +4 -4
- data/ext/Cargo.toml +8 -8
- data/ext/gqlitedb/Cargo.toml +0 -2
- data/ext/gqlitedb/src/compiler/variables_manager.rs +109 -10
- data/ext/gqlitedb/src/compiler.rs +481 -47
- data/ext/gqlitedb/src/connection.rs +12 -0
- data/ext/gqlitedb/src/consts.rs +1 -1
- data/ext/gqlitedb/src/error.rs +24 -0
- data/ext/gqlitedb/src/interpreter/evaluators.rs +91 -325
- data/ext/gqlitedb/src/interpreter/instructions.rs +72 -0
- data/ext/gqlitedb/src/interpreter/mod.rs +1 -0
- data/ext/gqlitedb/src/interpreter/path/bfs.rs +494 -0
- data/ext/gqlitedb/src/interpreter/path/dfs.rs +267 -0
- data/ext/gqlitedb/src/interpreter/path/group.rs +418 -0
- data/ext/gqlitedb/src/interpreter/path.rs +542 -0
- data/ext/gqlitedb/src/planner.rs +218 -6
- data/ext/gqlitedb/src/query_result.rs +19 -4
- data/ext/gqlitedb/src/tests/compiler.rs +2453 -1
- data/ext/gqlitedb/src/tests/connection/redb.rs +8 -0
- data/ext/gqlitedb/src/tests/connection.rs +37 -0
- data/ext/gqlitedb/src/tests/evaluators.rs +2206 -1
- data/ext/gqlitedb/src/tests/parser.rs +1225 -0
- data/ext/gqlitedb/src/tests/planner.rs +432 -2
- data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +5 -5
- data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +3 -1
- data/ext/gqlitedb/src/tests/templates/ast.rs +284 -1
- data/ext/gqlitedb/src/tests/templates/programs.rs +306 -0
- data/ext/gqliterb/Cargo.toml +0 -1
- data/ext/gqlparser/src/oc/ast.rs +52 -3
- data/ext/gqlparser/src/oc/error.rs +18 -0
- data/ext/gqlparser/src/oc/lexer.rs +146 -0
- data/ext/gqlparser/src/oc/parser/tests.rs +365 -20
- data/ext/gqlparser/src/oc/parser.rs +1196 -143
- data/ext/graphcore/src/value.rs +12 -1
- metadata +5 -2
- 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,
|
|
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 =
|
|
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
|
-
|
|
649
|
+
planner::PlannedPattern::Node(node) =>
|
|
573
650
|
{
|
|
574
651
|
self.compile_create_node(node, &mut instructions, &mut variables)?;
|
|
575
652
|
}
|
|
576
|
-
|
|
653
|
+
planner::PlannedPattern::Edge(edge) =>
|
|
577
654
|
{
|
|
578
655
|
self.compile_create_edge(edge, &mut instructions, &mut variables, None)?;
|
|
579
656
|
}
|
|
580
|
-
|
|
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
|
|
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
|
|
1274
|
+
fn compile_pattern_blocks(
|
|
873
1275
|
&mut self,
|
|
874
|
-
patterns: &[
|
|
875
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1281
|
+
patterns
|
|
1282
|
+
.iter()
|
|
1283
|
+
.map(|c| match c
|
|
884
1284
|
{
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
.
|
|
890
|
-
|
|
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
|
-
.
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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<
|
|
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(),
|
data/ext/gqlitedb/src/consts.rs
CHANGED
data/ext/gqlitedb/src/error.rs
CHANGED
|
@@ -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}.")]
|