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.
- checksums.yaml +4 -4
- data/ext/Cargo.toml +8 -8
- data/ext/db-index/Cargo.toml +2 -2
- data/ext/gqlitedb/Cargo.toml +0 -2
- data/ext/gqlitedb/src/aggregators.rs +28 -0
- data/ext/gqlitedb/src/compiler/expression_analyser.rs +33 -0
- data/ext/gqlitedb/src/compiler/variables_manager.rs +111 -11
- data/ext/gqlitedb/src/compiler.rs +506 -48
- data/ext/gqlitedb/src/connection.rs +14 -2
- data/ext/gqlitedb/src/consts.rs +1 -1
- data/ext/gqlitedb/src/error.rs +31 -0
- data/ext/gqlitedb/src/interpreter/evaluators.rs +153 -331
- data/ext/gqlitedb/src/interpreter/instructions.rs +77 -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 +231 -6
- data/ext/gqlitedb/src/query_result.rs +19 -4
- data/ext/gqlitedb/src/store/redb.rs +1 -1
- data/ext/gqlitedb/src/tests/compiler.rs +2454 -1
- data/ext/gqlitedb/src/tests/connection/postgres.rs +22 -0
- data/ext/gqlitedb/src/tests/connection/redb.rs +24 -0
- data/ext/gqlitedb/src/tests/connection/sqlite.rs +16 -0
- data/ext/gqlitedb/src/tests/connection.rs +101 -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 +433 -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 +294 -1
- data/ext/gqlitedb/src/tests/templates/programs.rs +318 -0
- data/ext/gqliterb/Cargo.toml +0 -1
- data/ext/gqlparser/src/oc/ast.rs +72 -3
- data/ext/gqlparser/src/oc/error.rs +18 -0
- data/ext/gqlparser/src/oc/lexer.rs +157 -2
- data/ext/gqlparser/src/oc/parser/tests.rs +735 -29
- data/ext/gqlparser/src/oc/parser.rs +1312 -182
- data/ext/gqlparser/src/oc/string.rs +172 -0
- data/ext/gqlparser/src/oc.rs +1 -0
- data/ext/graphcore/src/value/value_map.rs +2 -2
- data/ext/graphcore/src/value.rs +13 -1
- metadata +6 -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,
|
|
@@ -132,6 +214,7 @@ impl Compiler
|
|
|
132
214
|
init_instructions,
|
|
133
215
|
aggregator,
|
|
134
216
|
argument_instructions,
|
|
217
|
+
distinct: function_call.distinct,
|
|
135
218
|
},
|
|
136
219
|
));
|
|
137
220
|
Instruction::GetVariable { col_id: var_col_id }
|
|
@@ -317,6 +400,21 @@ impl Compiler
|
|
|
317
400
|
instructions.push(Instruction::IsNullUnaryOperator);
|
|
318
401
|
Instruction::NotUnaryOperator
|
|
319
402
|
}
|
|
403
|
+
ast::Expression::Contains(contains) =>
|
|
404
|
+
{
|
|
405
|
+
compile_binary_op!(self, contains, instructions, aggregations);
|
|
406
|
+
Instruction::StringContainsOperator
|
|
407
|
+
}
|
|
408
|
+
ast::Expression::StartsWith(starts_with) =>
|
|
409
|
+
{
|
|
410
|
+
compile_binary_op!(self, starts_with, instructions, aggregations);
|
|
411
|
+
Instruction::StringStartsWithOperator
|
|
412
|
+
}
|
|
413
|
+
ast::Expression::EndsWith(ends_with) =>
|
|
414
|
+
{
|
|
415
|
+
compile_binary_op!(self, ends_with, instructions, aggregations);
|
|
416
|
+
Instruction::StringEndsWithOperator
|
|
417
|
+
}
|
|
320
418
|
};
|
|
321
419
|
instructions.push(expr);
|
|
322
420
|
Ok(())
|
|
@@ -425,11 +523,6 @@ impl Compiler
|
|
|
425
523
|
.variables_manager
|
|
426
524
|
.mark_variables_as_set(&edge.variable)?;
|
|
427
525
|
self.compile_optional_expression(&edge.properties, instructions)?;
|
|
428
|
-
// Reject variable-length relationships in CREATE statements
|
|
429
|
-
if edge.recursive_range.is_some()
|
|
430
|
-
{
|
|
431
|
-
return Err(CompileTimeError::CreatingVarLength.into());
|
|
432
|
-
}
|
|
433
526
|
if !edge.labels.is_string()
|
|
434
527
|
{
|
|
435
528
|
Err(CompileTimeError::NoSingleRelationshipType {
|
|
@@ -545,7 +638,7 @@ impl Compiler
|
|
|
545
638
|
|
|
546
639
|
fn compile_create_patterns(
|
|
547
640
|
&mut self,
|
|
548
|
-
patterns: impl IntoIterator<Item =
|
|
641
|
+
patterns: impl IntoIterator<Item = planner::PlannedPattern>,
|
|
549
642
|
) -> Result<Block>
|
|
550
643
|
{
|
|
551
644
|
let actions = patterns.into_iter().map(|c| {
|
|
@@ -553,15 +646,15 @@ impl Compiler
|
|
|
553
646
|
let mut variables = Vec::<CreateActionVariable>::new();
|
|
554
647
|
match c
|
|
555
648
|
{
|
|
556
|
-
|
|
649
|
+
planner::PlannedPattern::Node(node) =>
|
|
557
650
|
{
|
|
558
651
|
self.compile_create_node(node, &mut instructions, &mut variables)?;
|
|
559
652
|
}
|
|
560
|
-
|
|
653
|
+
planner::PlannedPattern::Edge(edge) =>
|
|
561
654
|
{
|
|
562
655
|
self.compile_create_edge(edge, &mut instructions, &mut variables, None)?;
|
|
563
656
|
}
|
|
564
|
-
|
|
657
|
+
planner::PlannedPattern::Path(path) =>
|
|
565
658
|
{
|
|
566
659
|
self
|
|
567
660
|
.variables_manager
|
|
@@ -573,6 +666,10 @@ impl Compiler
|
|
|
573
666
|
Some(path.variable),
|
|
574
667
|
)?;
|
|
575
668
|
}
|
|
669
|
+
planner::PlannedPattern::QuantifiedPath { .. } =>
|
|
670
|
+
{
|
|
671
|
+
return Err(CompileTimeError::CreatingVarLength.into());
|
|
672
|
+
}
|
|
576
673
|
}
|
|
577
674
|
Ok(CreateAction {
|
|
578
675
|
instructions,
|
|
@@ -619,6 +716,26 @@ impl Compiler
|
|
|
619
716
|
filter.push(Instruction::AndBinaryOperator);
|
|
620
717
|
filter.push(Instruction::Swap);
|
|
621
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
|
+
}
|
|
622
739
|
instructions.push(Instruction::CreateNodeQuery { labels });
|
|
623
740
|
Ok(())
|
|
624
741
|
}
|
|
@@ -629,6 +746,7 @@ impl Compiler
|
|
|
629
746
|
edge: &gqlparser::oc::ast::EdgePattern,
|
|
630
747
|
single_match: bool,
|
|
631
748
|
previous_edges: &mut Vec<usize>,
|
|
749
|
+
recursive_range: Option<std::ops::Range<usize>>,
|
|
632
750
|
) -> Result<BlockMatch>
|
|
633
751
|
{
|
|
634
752
|
let mut instructions = Instructions::new();
|
|
@@ -681,6 +799,15 @@ impl Compiler
|
|
|
681
799
|
}
|
|
682
800
|
if self.variables_manager.is_set_variable(&edge.variable)?
|
|
683
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
|
+
}
|
|
684
811
|
instructions.push(Instruction::GetVariable {
|
|
685
812
|
col_id: self
|
|
686
813
|
.variables_manager
|
|
@@ -707,6 +834,23 @@ impl Compiler
|
|
|
707
834
|
filter.push(Instruction::AndBinaryOperator);
|
|
708
835
|
filter.push(Instruction::Swap);
|
|
709
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
|
+
}
|
|
710
854
|
instructions.push(Instruction::CreateEdgeQuery { labels });
|
|
711
855
|
}
|
|
712
856
|
|
|
@@ -764,7 +908,281 @@ impl Compiler
|
|
|
764
908
|
.get_variable_index_option(&path_variable)?,
|
|
765
909
|
filter,
|
|
766
910
|
directivity: edge.directivity,
|
|
767
|
-
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,
|
|
768
1186
|
})
|
|
769
1187
|
}
|
|
770
1188
|
|
|
@@ -853,45 +1271,77 @@ impl Compiler
|
|
|
853
1271
|
})
|
|
854
1272
|
}
|
|
855
1273
|
|
|
856
|
-
fn
|
|
1274
|
+
fn compile_pattern_blocks(
|
|
857
1275
|
&mut self,
|
|
858
|
-
patterns: &[
|
|
859
|
-
|
|
860
|
-
optional: bool,
|
|
861
|
-
) -> Result<Block>
|
|
1276
|
+
patterns: &[planner::PlannedPattern],
|
|
1277
|
+
) -> Result<Vec<BlockMatch>>
|
|
862
1278
|
{
|
|
863
1279
|
let is_single_match = patterns.len() == 1;
|
|
864
1280
|
let mut edge_variables = vec![];
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1281
|
+
patterns
|
|
1282
|
+
.iter()
|
|
1283
|
+
.map(|c| match c
|
|
868
1284
|
{
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
.
|
|
874
|
-
|
|
875
|
-
Ok(BlockMatch::MatchNode {
|
|
876
|
-
instructions,
|
|
877
|
-
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
|
|
878
1291
|
.variables_manager
|
|
879
|
-
.
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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)?;
|
|
895
1345
|
let mut filter = Instructions::new();
|
|
896
1346
|
if let Some(where_expression) = where_expression
|
|
897
1347
|
{
|
|
@@ -980,6 +1430,7 @@ impl Compiler
|
|
|
980
1430
|
},
|
|
981
1431
|
)?;
|
|
982
1432
|
Ok(instructions::Modifiers {
|
|
1433
|
+
distinct: modifiers.distinct,
|
|
983
1434
|
limit,
|
|
984
1435
|
skip,
|
|
985
1436
|
order_by,
|
|
@@ -988,7 +1439,7 @@ impl Compiler
|
|
|
988
1439
|
|
|
989
1440
|
fn unpack_scan_plan(
|
|
990
1441
|
logical_plan: LogicalPlan,
|
|
991
|
-
) -> Result<(Vec<
|
|
1442
|
+
) -> Result<(Vec<planner::PlannedPattern>, Option<ast::Expression>, bool)>
|
|
992
1443
|
{
|
|
993
1444
|
match logical_plan
|
|
994
1445
|
{
|
|
@@ -1100,6 +1551,12 @@ impl Compiler
|
|
|
1100
1551
|
projection.modifiers.limit = Some(expression);
|
|
1101
1552
|
Ok(projection)
|
|
1102
1553
|
}
|
|
1554
|
+
LogicalPlan::Distinct { source } =>
|
|
1555
|
+
{
|
|
1556
|
+
let mut projection = Self::unpack_projection_plan(*source)?;
|
|
1557
|
+
projection.modifiers.distinct = true;
|
|
1558
|
+
Ok(projection)
|
|
1559
|
+
}
|
|
1103
1560
|
_ => Err(
|
|
1104
1561
|
InternalError::Unreachable {
|
|
1105
1562
|
context: "compile/unpack_projection_plan",
|
|
@@ -1239,7 +1696,8 @@ pub(crate) fn compile(
|
|
|
1239
1696
|
| LogicalPlan::Sort { .. }
|
|
1240
1697
|
| LogicalPlan::Skip { .. }
|
|
1241
1698
|
| LogicalPlan::Limit { .. }
|
|
1242
|
-
| LogicalPlan::Filter { .. }
|
|
1699
|
+
| LogicalPlan::Filter { .. }
|
|
1700
|
+
| LogicalPlan::Distinct { .. } => compiler.compile_projection_plan(logical_plan),
|
|
1243
1701
|
LogicalPlan::Call { name, arguments } =>
|
|
1244
1702
|
{
|
|
1245
1703
|
let mut instructions = Instructions::new();
|