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
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
use rustc_hash::FxHashSet;
|
|
2
|
+
|
|
3
|
+
use crate::{
|
|
4
|
+
graph,
|
|
5
|
+
interpreter::{
|
|
6
|
+
evaluators::{Stack, TryPopInto, eval_instructions},
|
|
7
|
+
instructions,
|
|
8
|
+
},
|
|
9
|
+
prelude::*,
|
|
10
|
+
store,
|
|
11
|
+
value::Value,
|
|
12
|
+
value_table::{self, MutableRowInterface, RowInterface},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
pub(crate) mod bfs;
|
|
16
|
+
pub(crate) mod dfs;
|
|
17
|
+
pub(crate) mod group;
|
|
18
|
+
|
|
19
|
+
/// Extract every Edge-typed value bound among `group_variables` on `row`.
|
|
20
|
+
/// Used by both traversal strategies to determine which edges one
|
|
21
|
+
/// repetition consumed, for trail-uniqueness bookkeeping.
|
|
22
|
+
pub(crate) fn extract_edges(
|
|
23
|
+
row: &value_table::Row,
|
|
24
|
+
group_variables: &[value_table::ColId],
|
|
25
|
+
) -> Vec<graph::Key>
|
|
26
|
+
{
|
|
27
|
+
group_variables
|
|
28
|
+
.iter()
|
|
29
|
+
.filter_map(|col| row.get(*col).ok())
|
|
30
|
+
.filter_map(|val| match val
|
|
31
|
+
{
|
|
32
|
+
Value::Edge(e) => Some(e.key()),
|
|
33
|
+
_ => None,
|
|
34
|
+
})
|
|
35
|
+
.collect()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Resolves start nodes via `source_instructions`, then filters them
|
|
39
|
+
/// through `source_filter` if present. Identical logic regardless of
|
|
40
|
+
/// which traversal strategy follows.
|
|
41
|
+
#[allow(clippy::too_many_arguments)]
|
|
42
|
+
pub(crate) fn resolve_start_nodes<TStore: store::Store>(
|
|
43
|
+
source_instructions: &instructions::Instructions,
|
|
44
|
+
source_variable: &Option<value_table::ColId>,
|
|
45
|
+
source_filter: &instructions::Instructions,
|
|
46
|
+
store: &TStore,
|
|
47
|
+
tx: &mut TStore::TransactionBox,
|
|
48
|
+
graph_name: &str,
|
|
49
|
+
parameters: &crate::value::ValueMap,
|
|
50
|
+
row: &value_table::Row,
|
|
51
|
+
) -> Result<Vec<graph::Node>>
|
|
52
|
+
{
|
|
53
|
+
let mut start_nodes = Vec::new();
|
|
54
|
+
|
|
55
|
+
if !source_instructions.is_empty()
|
|
56
|
+
{
|
|
57
|
+
let mut eval_stack = Stack::default();
|
|
58
|
+
eval_instructions(&mut eval_stack, row, source_instructions, parameters)?;
|
|
59
|
+
let query: store::SelectNodeQuery = eval_stack.try_pop_into()?;
|
|
60
|
+
start_nodes = store.select_nodes(tx, graph_name, query)?;
|
|
61
|
+
}
|
|
62
|
+
else if let Some(src_col) = source_variable
|
|
63
|
+
&& let Value::Node(node) = row.get(*src_col)?
|
|
64
|
+
{
|
|
65
|
+
start_nodes.push(node.clone())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Apply source_filter to each start node
|
|
69
|
+
if !source_filter.is_empty()
|
|
70
|
+
{
|
|
71
|
+
start_nodes.retain(|node| {
|
|
72
|
+
let mut test_row = row.clone();
|
|
73
|
+
if let Some(sv) = source_variable
|
|
74
|
+
&& let Err(_) = test_row.set(*sv, node.clone().into())
|
|
75
|
+
{
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
let mut eval_stack = Stack::default();
|
|
79
|
+
match eval_instructions(&mut eval_stack, &test_row, source_filter, parameters)
|
|
80
|
+
{
|
|
81
|
+
Ok(_) => eval_stack.try_pop_into().unwrap_or(false),
|
|
82
|
+
Err(_) => false,
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Ok(start_nodes)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Checks `destination_filter` against `row`, if present.
|
|
91
|
+
/// Decision made here, not left inconsistent: a filter-evaluation
|
|
92
|
+
/// error is propagated (`?`), not silently treated as "doesn't pass" -
|
|
93
|
+
/// matching `bfs.rs`'s existing choice, not `group.rs`'s
|
|
94
|
+
/// `.unwrap_or(false)`. Swallowing an evaluation error into a silent
|
|
95
|
+
/// non-match would hide a real bug behind what looks like an ordinary
|
|
96
|
+
/// filtered-out candidate.
|
|
97
|
+
pub(crate) fn check_destination_filter(
|
|
98
|
+
destination_filter: &instructions::Instructions,
|
|
99
|
+
row: &value_table::Row,
|
|
100
|
+
parameters: &crate::value::ValueMap,
|
|
101
|
+
) -> Result<bool>
|
|
102
|
+
{
|
|
103
|
+
if destination_filter.is_empty()
|
|
104
|
+
{
|
|
105
|
+
return Ok(true);
|
|
106
|
+
}
|
|
107
|
+
let mut eval_stack = Stack::default();
|
|
108
|
+
eval_instructions(&mut eval_stack, row, destination_filter, parameters)?;
|
|
109
|
+
eval_stack.try_pop_into()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Binds the destination node and assembles the `path_variable`'s
|
|
113
|
+
/// `graph::Path`, reading scalar values from `candidate_row` - which
|
|
114
|
+
/// must be the row as returned from `execute_block_matches` for this
|
|
115
|
+
/// repetition, before `group_variables` columns are overwritten with
|
|
116
|
+
/// their accumulated arrays. This is the exact logic that diverged
|
|
117
|
+
/// between `group.rs` (correct) and `bfs.rs` (still using a
|
|
118
|
+
/// first-node-found heuristic) - extracting it removes the possibility
|
|
119
|
+
/// of that divergence happening again.
|
|
120
|
+
pub(crate) fn bind_destination_and_path(
|
|
121
|
+
candidate_row: &value_table::Row,
|
|
122
|
+
group_terminal_variable: value_table::ColId,
|
|
123
|
+
group_variables: &[value_table::ColId],
|
|
124
|
+
path_edges: &[graph::Edge],
|
|
125
|
+
start_node: &graph::Node,
|
|
126
|
+
) -> (Option<graph::Node>, Option<graph::Path>)
|
|
127
|
+
{
|
|
128
|
+
let terminal_node = match candidate_row.get(group_terminal_variable)
|
|
129
|
+
{
|
|
130
|
+
Ok(Value::Node(node)) => Some(node.clone()),
|
|
131
|
+
_ => None,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
let path = if !path_edges.is_empty()
|
|
135
|
+
{
|
|
136
|
+
let mut nodes = vec![start_node.clone()];
|
|
137
|
+
for var_col in group_variables
|
|
138
|
+
{
|
|
139
|
+
if let Ok(Value::Node(node)) = candidate_row.get(*var_col)
|
|
140
|
+
{
|
|
141
|
+
nodes.push(node.clone());
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let path = if nodes.len() > 1
|
|
145
|
+
{
|
|
146
|
+
graph::Path::new(
|
|
147
|
+
start_node.clone(),
|
|
148
|
+
path_edges
|
|
149
|
+
.iter()
|
|
150
|
+
.zip(nodes.iter().skip(1))
|
|
151
|
+
.map(|(e, n)| (e.clone(), n.clone()))
|
|
152
|
+
.collect(),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
{
|
|
157
|
+
graph::Path::new(start_node.clone(), vec![])
|
|
158
|
+
};
|
|
159
|
+
Some(path)
|
|
160
|
+
}
|
|
161
|
+
else
|
|
162
|
+
{
|
|
163
|
+
None
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
(terminal_node, path)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// can be reused within a single path traversal. `Trail` ensures no edge is
|
|
170
|
+
/// repeated (the only implemented variant).
|
|
171
|
+
#[derive(Debug, Clone, Copy)]
|
|
172
|
+
pub(crate) enum PathUniqueness
|
|
173
|
+
{
|
|
174
|
+
/// Trail uniqueness: no edge may be repeated within a single path.
|
|
175
|
+
/// This is the standard constraint for Cypher path expressions.
|
|
176
|
+
Trail,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
impl PathUniqueness
|
|
180
|
+
{
|
|
181
|
+
/// Check if an edge can be traversed given the current set of used edges.
|
|
182
|
+
/// For `Trail`, returns true iff the edge has not been used yet.
|
|
183
|
+
pub(crate) fn is_reusable(
|
|
184
|
+
&self,
|
|
185
|
+
used_edges: &FxHashSet<graph::Key>,
|
|
186
|
+
edge_key: &graph::Key,
|
|
187
|
+
) -> bool
|
|
188
|
+
{
|
|
189
|
+
match self
|
|
190
|
+
{
|
|
191
|
+
PathUniqueness::Trail => !used_edges.contains(edge_key),
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Mark an edge as used in the current path traversal.
|
|
196
|
+
/// For `Trail`, inserts the edge into the used set.
|
|
197
|
+
pub(crate) fn mark_used(&self, used_edges: &mut FxHashSet<graph::Key>, edge_key: graph::Key)
|
|
198
|
+
{
|
|
199
|
+
match self
|
|
200
|
+
{
|
|
201
|
+
PathUniqueness::Trail =>
|
|
202
|
+
{
|
|
203
|
+
used_edges.insert(edge_key);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// Mark an edge as unused when backtracking out of a branch.
|
|
209
|
+
/// For `Trail`, removes the edge from the used set for future branches.
|
|
210
|
+
pub(crate) fn mark_unused(&self, used_edges: &mut FxHashSet<graph::Key>, edge_key: &graph::Key)
|
|
211
|
+
{
|
|
212
|
+
match self
|
|
213
|
+
{
|
|
214
|
+
PathUniqueness::Trail =>
|
|
215
|
+
{
|
|
216
|
+
used_edges.remove(edge_key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Executes a `BlockMatch::MatchEdge` against a single row, appending any
|
|
223
|
+
/// resulting rows into `new_rows`.
|
|
224
|
+
#[allow(clippy::too_many_arguments)]
|
|
225
|
+
pub(crate) fn execute_match_edge<TStore: store::Store>(
|
|
226
|
+
block: &instructions::BlockMatch,
|
|
227
|
+
store: &TStore,
|
|
228
|
+
tx: &mut TStore::TransactionBox,
|
|
229
|
+
graph_name: &str,
|
|
230
|
+
stack: &mut Stack,
|
|
231
|
+
parameters: &crate::value::ValueMap,
|
|
232
|
+
row: &value_table::Row,
|
|
233
|
+
allow_recursive: bool,
|
|
234
|
+
new_rows: &mut Vec<value_table::Row>,
|
|
235
|
+
) -> Result<()>
|
|
236
|
+
{
|
|
237
|
+
let instructions::BlockMatch::MatchEdge {
|
|
238
|
+
instructions,
|
|
239
|
+
left_variable,
|
|
240
|
+
edge_variable,
|
|
241
|
+
right_variable,
|
|
242
|
+
path_variable,
|
|
243
|
+
filter,
|
|
244
|
+
directivity,
|
|
245
|
+
recursive_range,
|
|
246
|
+
} = block
|
|
247
|
+
else
|
|
248
|
+
{
|
|
249
|
+
unreachable!("execute_match_edge called with a non-MatchEdge block");
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
eval_instructions(stack, row, instructions, parameters)?;
|
|
253
|
+
let query = stack.try_pop_into()?;
|
|
254
|
+
|
|
255
|
+
match recursive_range
|
|
256
|
+
{
|
|
257
|
+
None =>
|
|
258
|
+
{
|
|
259
|
+
let edges = store.select_edges(tx, graph_name, query, *directivity)?;
|
|
260
|
+
|
|
261
|
+
for edge in edges.into_iter()
|
|
262
|
+
{
|
|
263
|
+
let mut new_row = row.clone();
|
|
264
|
+
let (left, right) = if edge.reversed
|
|
265
|
+
{
|
|
266
|
+
(edge.path.destination(), edge.path.source())
|
|
267
|
+
}
|
|
268
|
+
else
|
|
269
|
+
{
|
|
270
|
+
(edge.path.source(), edge.path.destination())
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if let Some(left_variable) = left_variable
|
|
274
|
+
{
|
|
275
|
+
new_row.set(*left_variable, left.to_owned().into())?;
|
|
276
|
+
}
|
|
277
|
+
if let Some(right_variable) = right_variable
|
|
278
|
+
{
|
|
279
|
+
new_row.set(*right_variable, right.to_owned().into())?;
|
|
280
|
+
}
|
|
281
|
+
if let Some(edge_variable) = edge_variable
|
|
282
|
+
{
|
|
283
|
+
new_row.set(*edge_variable, edge.path.to_edge().into())?;
|
|
284
|
+
}
|
|
285
|
+
if let Some(path_variable) = path_variable
|
|
286
|
+
{
|
|
287
|
+
new_row.set(*path_variable, edge.path.to_owned().into())?;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let should_add_row = if filter.is_empty()
|
|
291
|
+
{
|
|
292
|
+
true
|
|
293
|
+
}
|
|
294
|
+
else
|
|
295
|
+
{
|
|
296
|
+
let mut stack = Stack::default();
|
|
297
|
+
stack.push(true);
|
|
298
|
+
stack.push(edge.path.to_edge());
|
|
299
|
+
eval_instructions(&mut stack, &new_row, filter, parameters)?;
|
|
300
|
+
stack.try_pop()?;
|
|
301
|
+
stack.try_pop_into()?
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
if should_add_row
|
|
305
|
+
{
|
|
306
|
+
new_rows.push(new_row);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
Some(recursive_range) =>
|
|
311
|
+
{
|
|
312
|
+
dfs::execute(
|
|
313
|
+
recursive_range,
|
|
314
|
+
&query,
|
|
315
|
+
left_variable,
|
|
316
|
+
edge_variable,
|
|
317
|
+
right_variable,
|
|
318
|
+
path_variable,
|
|
319
|
+
filter,
|
|
320
|
+
*directivity,
|
|
321
|
+
store,
|
|
322
|
+
tx,
|
|
323
|
+
graph_name,
|
|
324
|
+
parameters,
|
|
325
|
+
row,
|
|
326
|
+
allow_recursive,
|
|
327
|
+
new_rows,
|
|
328
|
+
PathUniqueness::Trail,
|
|
329
|
+
)?;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
Ok(())
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/// Dispatch entry point for `MatchQuantifiedPath` execution.
|
|
337
|
+
/// Routes to the appropriate strategy handler (list-capture/DFS, BFS, etc.)
|
|
338
|
+
/// based on the `strategy` field.
|
|
339
|
+
///
|
|
340
|
+
/// This is called from `execute_block_match` when encountering a `MatchQuantifiedPath` block.
|
|
341
|
+
#[allow(clippy::too_many_arguments)]
|
|
342
|
+
pub(crate) fn execute_match_quantified_path<TStore: store::Store>(
|
|
343
|
+
block: &instructions::BlockMatch,
|
|
344
|
+
store: &TStore,
|
|
345
|
+
tx: &mut TStore::TransactionBox,
|
|
346
|
+
graph_name: &str,
|
|
347
|
+
stack: &mut Stack,
|
|
348
|
+
parameters: &crate::value::ValueMap,
|
|
349
|
+
row: &value_table::Row,
|
|
350
|
+
allow_recursive: bool,
|
|
351
|
+
new_rows: &mut Vec<value_table::Row>,
|
|
352
|
+
) -> Result<()>
|
|
353
|
+
{
|
|
354
|
+
let instructions::BlockMatch::MatchQuantifiedPath { strategy, .. } = block
|
|
355
|
+
else
|
|
356
|
+
{
|
|
357
|
+
unreachable!("execute_match_quantified_path called with a non-MatchQuantifiedPath block");
|
|
358
|
+
};
|
|
359
|
+
match strategy
|
|
360
|
+
{
|
|
361
|
+
instructions::PathStrategy::Dfs => group::execute(
|
|
362
|
+
block,
|
|
363
|
+
store,
|
|
364
|
+
tx,
|
|
365
|
+
graph_name,
|
|
366
|
+
stack,
|
|
367
|
+
parameters,
|
|
368
|
+
row,
|
|
369
|
+
allow_recursive,
|
|
370
|
+
new_rows,
|
|
371
|
+
),
|
|
372
|
+
instructions::PathStrategy::Bfs { .. } => bfs::execute(
|
|
373
|
+
block,
|
|
374
|
+
store,
|
|
375
|
+
tx,
|
|
376
|
+
graph_name,
|
|
377
|
+
stack,
|
|
378
|
+
parameters,
|
|
379
|
+
row,
|
|
380
|
+
allow_recursive,
|
|
381
|
+
new_rows,
|
|
382
|
+
),
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#[cfg(test)]
|
|
387
|
+
mod tests
|
|
388
|
+
{
|
|
389
|
+
use super::*;
|
|
390
|
+
use crate::value::Value;
|
|
391
|
+
use crate::value_table::{ColId, MutableRowInterface, Row};
|
|
392
|
+
|
|
393
|
+
#[test]
|
|
394
|
+
fn test_extract_edges_mixed_types()
|
|
395
|
+
{
|
|
396
|
+
// Create a row with capacity for 4 columns
|
|
397
|
+
let mut row = Row::new(vec![], 4);
|
|
398
|
+
let col_edge1 = ColId::from(0usize);
|
|
399
|
+
let col_edge2 = ColId::from(1usize);
|
|
400
|
+
let col_node = ColId::from(2usize);
|
|
401
|
+
let col_other = ColId::from(3usize);
|
|
402
|
+
|
|
403
|
+
// Create test edges and nodes using default constructors
|
|
404
|
+
let edge1 = graph::Edge::new(None, Default::default(), vec![], Default::default());
|
|
405
|
+
let edge2 = graph::Edge::new(None, Default::default(), vec![], Default::default());
|
|
406
|
+
let node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
407
|
+
|
|
408
|
+
// Set values in row
|
|
409
|
+
row.set(col_edge1, Value::Edge(edge1.clone())).unwrap();
|
|
410
|
+
row.set(col_edge2, Value::Edge(edge2.clone())).unwrap();
|
|
411
|
+
row.set(col_node, Value::Node(node)).unwrap();
|
|
412
|
+
row.set(col_other, Value::Null).unwrap();
|
|
413
|
+
|
|
414
|
+
let group_variables = vec![col_edge1, col_edge2, col_node, col_other];
|
|
415
|
+
let edges = extract_edges(&row, &group_variables);
|
|
416
|
+
|
|
417
|
+
// Should only extract the two edges
|
|
418
|
+
assert_eq!(edges.len(), 2);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
#[test]
|
|
422
|
+
fn test_extract_edges_no_edges()
|
|
423
|
+
{
|
|
424
|
+
let mut row = Row::new(vec![], 2);
|
|
425
|
+
let col_node = ColId::from(0usize);
|
|
426
|
+
let col_other = ColId::from(1usize);
|
|
427
|
+
|
|
428
|
+
let node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
429
|
+
row.set(col_node, Value::Node(node)).unwrap();
|
|
430
|
+
row.set(col_other, Value::Null).unwrap();
|
|
431
|
+
|
|
432
|
+
let group_variables = vec![col_node, col_other];
|
|
433
|
+
let edges = extract_edges(&row, &group_variables);
|
|
434
|
+
|
|
435
|
+
assert_eq!(edges.len(), 0);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
#[test]
|
|
439
|
+
fn test_check_destination_filter_empty_filter()
|
|
440
|
+
{
|
|
441
|
+
let row = Row::new(vec![], 0);
|
|
442
|
+
let empty_filter = instructions::Instructions::default();
|
|
443
|
+
let params = Default::default();
|
|
444
|
+
|
|
445
|
+
let result = check_destination_filter(&empty_filter, &row, ¶ms).unwrap();
|
|
446
|
+
assert!(result, "Empty filter should always return true");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
#[test]
|
|
450
|
+
fn test_bind_destination_and_path_with_edges()
|
|
451
|
+
{
|
|
452
|
+
let mut candidate_row = Row::new(vec![], 2);
|
|
453
|
+
let group_term_col = ColId::from(0usize);
|
|
454
|
+
let group_var_col = ColId::from(1usize);
|
|
455
|
+
|
|
456
|
+
let terminal_node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
457
|
+
let intermediate_node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
458
|
+
let start_node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
459
|
+
|
|
460
|
+
candidate_row
|
|
461
|
+
.set(group_term_col, Value::Node(terminal_node.clone()))
|
|
462
|
+
.unwrap();
|
|
463
|
+
candidate_row
|
|
464
|
+
.set(group_var_col, Value::Node(intermediate_node.clone()))
|
|
465
|
+
.unwrap();
|
|
466
|
+
|
|
467
|
+
let edge = graph::Edge::new(None, Default::default(), vec![], Default::default());
|
|
468
|
+
|
|
469
|
+
let group_variables = vec![group_var_col];
|
|
470
|
+
let path_edges = vec![edge.clone()];
|
|
471
|
+
|
|
472
|
+
let (dest_node, path) = bind_destination_and_path(
|
|
473
|
+
&candidate_row,
|
|
474
|
+
group_term_col,
|
|
475
|
+
&group_variables,
|
|
476
|
+
&path_edges,
|
|
477
|
+
&start_node,
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
assert_eq!(dest_node.unwrap().key(), terminal_node.key());
|
|
481
|
+
assert!(path.is_some());
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#[test]
|
|
485
|
+
fn test_bind_destination_and_path_no_edges()
|
|
486
|
+
{
|
|
487
|
+
let mut candidate_row = Row::new(vec![], 1);
|
|
488
|
+
let group_term_col = ColId::from(0usize);
|
|
489
|
+
|
|
490
|
+
let terminal_node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
491
|
+
let start_node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
492
|
+
|
|
493
|
+
candidate_row
|
|
494
|
+
.set(group_term_col, Value::Node(terminal_node.clone()))
|
|
495
|
+
.unwrap();
|
|
496
|
+
|
|
497
|
+
let group_variables = vec![];
|
|
498
|
+
let path_edges = vec![];
|
|
499
|
+
|
|
500
|
+
let (dest_node, path) = bind_destination_and_path(
|
|
501
|
+
&candidate_row,
|
|
502
|
+
group_term_col,
|
|
503
|
+
&group_variables,
|
|
504
|
+
&path_edges,
|
|
505
|
+
&start_node,
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
assert_eq!(dest_node.unwrap().key(), terminal_node.key());
|
|
509
|
+
assert!(
|
|
510
|
+
path.is_none(),
|
|
511
|
+
"Path should be None when path_edges is empty"
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#[test]
|
|
516
|
+
fn test_bind_destination_and_path_missing_terminal()
|
|
517
|
+
{
|
|
518
|
+
let candidate_row = Row::new(vec![], 1);
|
|
519
|
+
let group_term_col = ColId::from(0usize);
|
|
520
|
+
let start_node = graph::Node::new(None, Default::default(), vec![], Default::default());
|
|
521
|
+
|
|
522
|
+
let group_variables = vec![];
|
|
523
|
+
let path_edges = vec![];
|
|
524
|
+
|
|
525
|
+
let (dest_node, path) = bind_destination_and_path(
|
|
526
|
+
&candidate_row,
|
|
527
|
+
group_term_col,
|
|
528
|
+
&group_variables,
|
|
529
|
+
&path_edges,
|
|
530
|
+
&start_node,
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
assert!(
|
|
534
|
+
dest_node.is_none(),
|
|
535
|
+
"Destination node should be None if not set"
|
|
536
|
+
);
|
|
537
|
+
assert!(
|
|
538
|
+
path.is_none(),
|
|
539
|
+
"Path should be None when path_edges is empty"
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|