Skip to content

Commit 189a16a

Browse files
authored
Merge pull request #331 from korpling/fix-execute-query-on-graph
execute_query_on_graph should ignore optional nodes and generate unique results
2 parents 72d12db + 5b33111 commit 189a16a

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55

66
## [Unreleased]
77

8+
### Fixed
9+
10+
- `execute_query_on_graph` now skips nodes that are not part of the output
11+
(optional negated nodes) and makes sure the resulting iterator only produces
12+
unique results.
13+
814
## [4.0.0] - 2025-08-20
915

1016
### Changed

graphannis/src/annis/db/aql/mod.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod operators;
66

77
use boolean_expression::Expr;
88
use graphannis_core::annostorage::MatchGroup;
9+
use itertools::Itertools;
910
lalrpop_mod!(
1011
#[allow(clippy::all)]
1112
#[allow(clippy::panic)]
@@ -77,7 +78,28 @@ pub fn execute_query_on_graph<'a>(
7778
let timeout = TimeoutCheck::new(timeout);
7879

7980
let config = Config { use_parallel_joins };
80-
let it = ExecutionPlan::from_disjunction(query, graph, &config, timeout)?;
81+
let it = ExecutionPlan::from_disjunction(query, graph, &config, timeout)?
82+
.map_ok(|mg| {
83+
// Rewrite match group to only include nodes that are included in the query
84+
// check if query node actually should be included
85+
let mut new_match_group = MatchGroup::with_capacity(mg.len());
86+
for (node_nr, m) in mg.into_iter().enumerate() {
87+
let include_in_output = query
88+
.get_variable_by_node_nr(node_nr)
89+
.is_some_and(|var| query.is_included_in_output(&var));
90+
if include_in_output {
91+
new_match_group.push(m);
92+
}
93+
}
94+
new_match_group
95+
})
96+
.unique_by(move |mg| {
97+
// Map an error to an empty match group
98+
match mg {
99+
Ok(mg) => mg.clone(),
100+
Err(_) => MatchGroup::new(),
101+
}
102+
});
81103

82104
Ok(Box::from(it))
83105
}
@@ -663,4 +685,25 @@ mod tests {
663685
let it = execute_query_on_graph(&graph, &query, true, None).unwrap();
664686
assert_eq!(11, it.count());
665687
}
688+
689+
#[test]
690+
fn query_on_annotation_graph_with_negation() {
691+
let cargo_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
692+
let input_file = File::open(&cargo_dir.join("tests/SaltSampleCorpus.graphml")).unwrap();
693+
let (graph, _config_str): (AnnotationGraph, _) =
694+
graphannis_core::graph::serialization::graphml::import(input_file, false, |_status| {})
695+
.unwrap();
696+
697+
let query = parse("tok? !. tok", false).unwrap();
698+
let it = execute_query_on_graph(&graph, &query, true, None).unwrap();
699+
700+
let matches: Result<Vec<_>> = it.collect();
701+
let matches = matches.unwrap();
702+
assert_eq!(4, matches.len());
703+
// The match should not have 2 two nodes, but only one nde
704+
assert_eq!(1, matches[0].len());
705+
assert_eq!(1, matches[1].len());
706+
assert_eq!(1, matches[2].len());
707+
assert_eq!(1, matches[3].len());
708+
}
666709
}

0 commit comments

Comments
 (0)