From 2fca93829d556280c6a481f3095f4ac03cfa3407 Mon Sep 17 00:00:00 2001 From: Martin Klotz Date: Fri, 9 Oct 2026 11:08:42 +0200 Subject: [PATCH 01/10] for performance reasons, it is always best to duplicate named dominance edges in the unnamed default dominance component. To keep this load off the corpus building side, it is integrated into apply_update_graph_index of AnnotationComponent --- graphannis/src/annis/db/aql/model.rs | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/graphannis/src/annis/db/aql/model.rs b/graphannis/src/annis/db/aql/model.rs index 4ad880787..966ed2b5f 100644 --- a/graphannis/src/annis/db/aql/model.rs +++ b/graphannis/src/annis/db/aql/model.rs @@ -12,8 +12,9 @@ use graphannis_core::{ types::ComponentType, util::disk_collections::{DEFAULT_BLOCK_CACHE_CAPACITY, DiskMap, EvictionStrategy}, }; +use itertools::Itertools; use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, BTreeSet, HashMap}, fmt, }; use transient_btree_index::BtreeConfig; @@ -597,6 +598,37 @@ impl ComponentType for AnnotationComponentType { index.reindex_inherited_coverage(graph, gs_order)?; } + // make sure all edges in named dominance components are also in the default dominance component `Dominance/annis` + { + let default_dominance_component = AnnotationComponent::new( + AnnotationComponentType::Dominance, + ANNIS_NS.into(), + "".into(), + ); + let mut edge_collector = BTreeSet::default(); + let all_dominance_components = + graph.get_all_components(Some(AnnotationComponentType::Dominance), None); + for component in all_dominance_components { + if component == default_dominance_component { + continue; + } + if let Some(other_dominance_gs) = graph.get_graphstorage_as_ref(&component) { + for source_node in other_dominance_gs.source_nodes() { + let source = source_node?; + for target in other_dominance_gs.get_outgoing_edges(source) { + let target = target?; + edge_collector.insert(Edge { source, target }); + } + } + } + } + let default_dominance_gs = + graph.get_or_create_writable(&default_dominance_component)?; + edge_collector + .into_iter() + .try_for_each(|e| default_dominance_gs.add_edge(e))?; + } + Ok(()) } From 6cf783f9766c74bbf9cd46a7dbda5f3c097b775d Mon Sep 17 00:00:00 2001 From: Martin Klotz Date: Fri, 9 Oct 2026 11:13:40 +0200 Subject: [PATCH 02/10] update --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2af566285..c2b6262cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `AnnotationComponentType::apply_update_graph_index` makes sure all edges + from named dominance components are also contained in the default + dominance component `Dominance/annis`. + ## [4.2.0] - 2026-08-11 ### Added From de484e0a44943384c39ee4ecdf1465b616e7e8e3 Mon Sep 17 00:00:00 2001 From: Martin Klotz Date: Fri, 9 Oct 2026 11:15:15 +0200 Subject: [PATCH 03/10] remove unused import --- graphannis/src/annis/db/aql/model.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphannis/src/annis/db/aql/model.rs b/graphannis/src/annis/db/aql/model.rs index 966ed2b5f..f8c54325c 100644 --- a/graphannis/src/annis/db/aql/model.rs +++ b/graphannis/src/annis/db/aql/model.rs @@ -12,7 +12,6 @@ use graphannis_core::{ types::ComponentType, util::disk_collections::{DEFAULT_BLOCK_CACHE_CAPACITY, DiskMap, EvictionStrategy}, }; -use itertools::Itertools; use std::{ collections::{BTreeMap, BTreeSet, HashMap}, fmt, @@ -599,7 +598,7 @@ impl ComponentType for AnnotationComponentType { } // make sure all edges in named dominance components are also in the default dominance component `Dominance/annis` - { + { let default_dominance_component = AnnotationComponent::new( AnnotationComponentType::Dominance, ANNIS_NS.into(), From f8598019f80f746b84e1106b34b013e8bc8e5405 Mon Sep 17 00:00:00 2001 From: Martin Klotz Date: Fri, 9 Oct 2026 11:15:52 +0200 Subject: [PATCH 04/10] updated snapshot --- ...__tests__add_token_to_single_sentence.snap | 120 +++++++++++------- 1 file changed, 72 insertions(+), 48 deletions(-) diff --git a/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap b/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap index 5951c5264..47bd3a76c 100644 --- a/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap +++ b/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap @@ -106,101 +106,125 @@ expression: "String::from_utf8_lossy(&output)" node - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + From 24e67c907560399e9afc30e8167ac885719bc798 Mon Sep 17 00:00:00 2001 From: Thomas Krause Date: Mon, 12 Oct 2026 10:50:05 +0200 Subject: [PATCH 05/10] Add a benchmark for applying updates to a graph with dominance edges --- graphannis/benches/graphannis.rs | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/graphannis/benches/graphannis.rs b/graphannis/benches/graphannis.rs index 8603e52a1..ac912eedb 100644 --- a/graphannis/benches/graphannis.rs +++ b/graphannis/benches/graphannis.rs @@ -13,8 +13,10 @@ use fake::locales::*; use graphannis::CorpusStorage; use graphannis::corpusstorage::ResultOrder; use graphannis::corpusstorage::{QueryLanguage, SearchQuery}; +use graphannis::model::AnnotationComponentType; use graphannis::update::{GraphUpdate, UpdateEvent}; use std::collections::HashSet; +use std::io::BufReader; use std::path::Path; use std::path::PathBuf; @@ -429,6 +431,39 @@ fn apply_update_ondisk(bench: &mut Criterion) { }); } +/// Benchmark a more complex graph with dominance and pointing relations. +fn apply_update_complex_graph(bench: &mut Criterion) { + if CORPUS_STORAGE.is_none() { + return; + } + + let cs = CORPUS_STORAGE.as_ref().unwrap(); + + bench.bench_function("apply_update_complex_graph", move |b| { + cs.create_empty_corpus("apply_update_test_corpus", false) + .unwrap(); + + let mut input = + BufReader::new(&include_bytes!("../../core/tests/single_sentence.graphml")[..]); + let mut node_updates = GraphUpdate::new(); + let mut edge_updates = GraphUpdate::new(); + let _config_str = graphannis_core::graph::serialization::graphml::read_graphml::< + AnnotationComponentType, + _, + _, + >(&mut input, &mut node_updates, &mut edge_updates, &|_| {}) + .unwrap(); + + b.iter(|| { + cs.apply_update("apply_update_test_corpus", &mut node_updates) + .unwrap(); + cs.apply_update("apply_update_test_corpus", &mut edge_updates) + .unwrap(); + }); + cs.delete("apply_update_test_corpus").unwrap(); + }); +} + criterion_group!(name=default; config= Criterion::default().sample_size(50); targets = deserialize_gum, find_first_ten_token_gum, @@ -441,5 +476,6 @@ criterion_group!(name=default; config= Criterion::default().sample_size(50); tar criterion_group!(name=apply_update; config= Criterion::default().sample_size(20); targets = apply_update_inmemory, apply_update_ondisk, + apply_update_complex_graph, ); criterion_main!(default, apply_update); From 2b643ec53113d08a75a0b94aba3631e7700acff1 Mon Sep 17 00:00:00 2001 From: Thomas Krause Date: Mon, 12 Oct 2026 14:04:27 +0200 Subject: [PATCH 06/10] Directly write the edge to the component instead of collecting it first --- graphannis/src/annis/db/aql/model.rs | 34 ++++++++++++---------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/graphannis/src/annis/db/aql/model.rs b/graphannis/src/annis/db/aql/model.rs index f8c54325c..80cbb85ef 100644 --- a/graphannis/src/annis/db/aql/model.rs +++ b/graphannis/src/annis/db/aql/model.rs @@ -604,28 +604,24 @@ impl ComponentType for AnnotationComponentType { ANNIS_NS.into(), "".into(), ); - let mut edge_collector = BTreeSet::default(); - let all_dominance_components = - graph.get_all_components(Some(AnnotationComponentType::Dominance), None); - for component in all_dominance_components { - if component == default_dominance_component { - continue; - } - if let Some(other_dominance_gs) = graph.get_graphstorage_as_ref(&component) { - for source_node in other_dominance_gs.source_nodes() { - let source = source_node?; - for target in other_dominance_gs.get_outgoing_edges(source) { - let target = target?; - edge_collector.insert(Edge { source, target }); - } + + let other_dominance_components: Vec<_> = graph + .get_all_components(Some(AnnotationComponentType::Dominance), None) + .into_iter() + .filter(|c| c != &default_dominance_component) + .filter_map(|c| graph.get_graphstorage(&c)) + .collect(); + let default_dominance_gs = + graph.get_or_create_writable(&default_dominance_component)?; + for other_dominance_gs in other_dominance_components { + for source_node in other_dominance_gs.source_nodes() { + let source = source_node?; + for target in other_dominance_gs.get_outgoing_edges(source) { + let target = target?; + default_dominance_gs.add_edge(Edge { source, target })?; } } } - let default_dominance_gs = - graph.get_or_create_writable(&default_dominance_component)?; - edge_collector - .into_iter() - .try_for_each(|e| default_dominance_gs.add_edge(e))?; } Ok(()) From 3f6ffb32e34a68c278d762ee8b8a1f7d55e839d0 Mon Sep 17 00:00:00 2001 From: Thomas Krause Date: Mon, 12 Oct 2026 17:57:54 +0200 Subject: [PATCH 07/10] Only add a new edge to the default dominance component if edge is not in any other unnamed component --- graphannis/src/annis/db/aql/model.rs | 39 +++++- ...__tests__add_token_to_single_sentence.snap | 120 +++++++----------- 2 files changed, 80 insertions(+), 79 deletions(-) diff --git a/graphannis/src/annis/db/aql/model.rs b/graphannis/src/annis/db/aql/model.rs index 80cbb85ef..51ee4924e 100644 --- a/graphannis/src/annis/db/aql/model.rs +++ b/graphannis/src/annis/db/aql/model.rs @@ -7,13 +7,13 @@ use facet::Facet; use graphannis_core::{ annostorage::ValueSearch, dfs::CycleSafeDFS, - errors::ComponentTypeError, + errors::{ComponentTypeError, Result}, graph::{ANNIS_NS, NODE_TYPE_KEY, storage::union::UnionEdgeContainer}, types::ComponentType, util::disk_collections::{DEFAULT_BLOCK_CACHE_CAPACITY, DiskMap, EvictionStrategy}, }; use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeMap, HashMap, HashSet}, fmt, }; use transient_btree_index::BtreeConfig; @@ -605,25 +605,50 @@ impl ComponentType for AnnotationComponentType { "".into(), ); - let other_dominance_components: Vec<_> = graph - .get_all_components(Some(AnnotationComponentType::Dominance), None) + let existing_dominance_components: Vec<_> = + graph.get_all_components(Some(AnnotationComponentType::Dominance), None); + + let (unnamed_dominance_components, named_dominance_componts): (Vec<_>, Vec<_>) = + existing_dominance_components + .into_iter() + .filter(|c| c != &default_dominance_component) + .partition(|c| c.name.is_empty()); + + let named_dominance_gs: Vec<_> = named_dominance_componts .into_iter() - .filter(|c| c != &default_dominance_component) .filter_map(|c| graph.get_graphstorage(&c)) .collect(); + + let unnamed_dominance_gs: Vec<_> = unnamed_dominance_components + .into_iter() + .filter_map(|c| graph.get_graphstorage(&c)) + .collect(); + let default_dominance_gs = graph.get_or_create_writable(&default_dominance_component)?; - for other_dominance_gs in other_dominance_components { + for other_dominance_gs in named_dominance_gs { for source_node in other_dominance_gs.source_nodes() { let source = source_node?; + // The ">" operator will search all components that have an empty name. + // We don't need to add an edge if its already part of such a component. + let existing_unnamed_outgoing: Result> = unnamed_dominance_gs + .iter() + .flat_map(|gs| gs.get_outgoing_edges(source)) + .collect(); + let existing_unnamed_outgoing = + HashSet::::from_iter(existing_unnamed_outgoing?); for target in other_dominance_gs.get_outgoing_edges(source) { let target = target?; - default_dominance_gs.add_edge(Edge { source, target })?; + if !existing_unnamed_outgoing.contains(&target) { + default_dominance_gs.add_edge(Edge { source, target })?; + } } } } } + // TODO: delete default graph storage when empty + Ok(()) } diff --git a/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap b/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap index 47bd3a76c..5951c5264 100644 --- a/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap +++ b/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_token_to_single_sentence.snap @@ -106,125 +106,101 @@ expression: "String::from_utf8_lossy(&output)" node - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + From 6dd84c37e6aa6d98c5a4d05798f3cb1109b89fe6 Mon Sep 17 00:00:00 2001 From: Thomas Krause Date: Mon, 12 Oct 2026 18:21:33 +0200 Subject: [PATCH 08/10] Add explicit test that the edges are added --- ...__add_dominance_edges_with_empty_name.snap | 198 +++++++++++++++++ graphannis/src/annis/db/aql/model/tests.rs | 25 ++- .../single_sentence only_named_edges.graphml | 205 ++++++++++++++++++ 3 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_dominance_edges_with_empty_name.snap create mode 100644 graphannis/tests/single_sentence only_named_edges.graphml diff --git a/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_dominance_edges_with_empty_name.snap b/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_dominance_edges_with_empty_name.snap new file mode 100644 index 000000000..561a64afb --- /dev/null +++ b/graphannis/src/annis/db/aql/model/snapshots/graphannis__annis__db__aql__model__tests__add_dominance_edges_with_empty_name.snap @@ -0,0 +1,198 @@ +--- +source: graphannis/src/annis/db/aql/model/tests.rs +expression: "String::from_utf8_lossy(&output)" +--- + + + + + + + + + + + + corpus + + + zossen + corpus + + + datasource + + + ROOT + syntax + node + + + S + syntax + node + + + NP + syntax + node + + + default_layer + node + ART + Die + + + default_layer + node + NN + Jugendlichen + + + + PP + syntax + node + + + default_layer + node + APPR + in + + + + default_layer + node + NE + Zossen + + + + default_layer + node + VMFIN + wollen + + + + NP + syntax + node + + + default_layer + node + ART + ein + + + + default_layer + node + NN + Musikcafé + + + + default_layer + node + $. + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphannis/src/annis/db/aql/model/tests.rs b/graphannis/src/annis/db/aql/model/tests.rs index 5373757f8..a1638cc74 100644 --- a/graphannis/src/annis/db/aql/model/tests.rs +++ b/graphannis/src/annis/db/aql/model/tests.rs @@ -28,11 +28,11 @@ fn global_stats_token_count() { assert_eq!(true, graph.global_statistics.is_some()); let global_stats = graph.global_statistics.unwrap(); assert_eq!(true, global_stats.all_token_in_order_component); - assert_matches!(global_stats.corpus_size, + assert_matches!(global_stats.corpus_size, CorpusSize::Token { base_token_count, segmentation_count } if base_token_count == 16 && segmentation_count.len() == 2 - && *segmentation_count.get("diplomatic").unwrap() == 11 + && *segmentation_count.get("diplomatic").unwrap() == 11 && *segmentation_count.get("norm").unwrap() == 13); } @@ -345,3 +345,24 @@ fn add_token_to_single_sentence() { .unwrap(); assert_snapshot!(String::from_utf8_lossy(&output)); } + +#[test] +fn add_dominance_edges_with_empty_name() { + let content = + &include_bytes!("../../../../../tests/single_sentence only_named_edges.graphml")[..]; + let (graph, _config) = + graphml::import::(content, false, |_| {}).unwrap(); + + let mut output = Vec::::default(); + graphannis_core::graph::serialization::graphml::export_stable_order( + &graph, + None, + &mut output, + |_| {}, + ) + .unwrap(); + + // The output has changed, because new edges have been added. + assert_ne!(content, output); + assert_snapshot!(String::from_utf8_lossy(&output)); +} diff --git a/graphannis/tests/single_sentence only_named_edges.graphml b/graphannis/tests/single_sentence only_named_edges.graphml new file mode 100644 index 000000000..2eda62789 --- /dev/null +++ b/graphannis/tests/single_sentence only_named_edges.graphml @@ -0,0 +1,205 @@ + + + + + + + + + + + + corpus + + + zossen + corpus + + + datasource + + + ROOT + syntax + node + + + S + syntax + node + + + NP + syntax + node + + + default_layer + node + ART + Die + + + default_layer + node + NN + Jugendlichen + + + + PP + syntax + node + + + default_layer + node + APPR + in + + + + default_layer + node + NE + Zossen + + + + default_layer + node + VMFIN + wollen + + + + NP + syntax + node + + + default_layer + node + ART + ein + + + + default_layer + node + NN + Musikcafé + + + + default_layer + node + $. + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c6e50e7646d356f1cd45db5b29c2e0769e95040d Mon Sep 17 00:00:00 2001 From: Thomas Krause Date: Mon, 12 Oct 2026 19:15:57 +0200 Subject: [PATCH 09/10] Move to member function --- graphannis/src/annis/db/aql/model.rs | 108 +++++++++--------- graphannis/src/annis/db/aql/model/tests.rs | 2 +- ... single_sentence_only_named_edges.graphml} | 0 3 files changed, 57 insertions(+), 53 deletions(-) rename graphannis/tests/{single_sentence only_named_edges.graphml => single_sentence_only_named_edges.graphml} (100%) diff --git a/graphannis/src/annis/db/aql/model.rs b/graphannis/src/annis/db/aql/model.rs index 51ee4924e..a695deade 100644 --- a/graphannis/src/annis/db/aql/model.rs +++ b/graphannis/src/annis/db/aql/model.rs @@ -393,6 +393,60 @@ impl AQLUpdateGraphIndex { Ok(None) } } + + /// Make sure all edges in named dominance components are in an unnamed dominance component. + fn add_empty_dominance_edges( + &mut self, + graph: &mut AnnotationGraph, + ) -> std::result::Result<(), ComponentTypeError> { + let default_dominance_component = AnnotationComponent::new( + AnnotationComponentType::Dominance, + ANNIS_NS.into(), + "".into(), + ); + + let existing_dominance_components: Vec<_> = + graph.get_all_components(Some(AnnotationComponentType::Dominance), None); + + let (unnamed_dominance_components, named_dominance_componts): (Vec<_>, Vec<_>) = + existing_dominance_components + .into_iter() + .filter(|c| c != &default_dominance_component) + .partition(|c| c.name.is_empty()); + + let named_dominance_gs: Vec<_> = named_dominance_componts + .into_iter() + .filter_map(|c| graph.get_graphstorage(&c)) + .collect(); + + let unnamed_dominance_gs: Vec<_> = unnamed_dominance_components + .into_iter() + .filter_map(|c| graph.get_graphstorage(&c)) + .collect(); + + let default_dominance_gs = graph.get_or_create_writable(&default_dominance_component)?; + for other_dominance_gs in named_dominance_gs { + for source_node in other_dominance_gs.source_nodes() { + let source = source_node?; + // The ">" operator will search all components that have an empty name. + // We don't need to add an edge if its already part of such a component. + let existing_unnamed_outgoing: Result> = unnamed_dominance_gs + .iter() + .flat_map(|gs| gs.get_outgoing_edges(source)) + .collect(); + let existing_unnamed_outgoing = + HashSet::::from_iter(existing_unnamed_outgoing?); + for target in other_dominance_gs.get_outgoing_edges(source) { + let target = target?; + if !existing_unnamed_outgoing.contains(&target) { + default_dominance_gs.add_edge(Edge { source, target })?; + } + } + } + } + + Ok(()) + } } impl ComponentType for AnnotationComponentType { @@ -578,6 +632,8 @@ impl ComponentType for AnnotationComponentType { } index.invalid_nodes.compact()?; + index.add_empty_dominance_edges(graph)?; + // Re-index the inherited coverage component. // To make this operation fast, we need to optimize the order component first let order_component = AnnotationComponent::new( @@ -597,58 +653,6 @@ impl ComponentType for AnnotationComponentType { index.reindex_inherited_coverage(graph, gs_order)?; } - // make sure all edges in named dominance components are also in the default dominance component `Dominance/annis` - { - let default_dominance_component = AnnotationComponent::new( - AnnotationComponentType::Dominance, - ANNIS_NS.into(), - "".into(), - ); - - let existing_dominance_components: Vec<_> = - graph.get_all_components(Some(AnnotationComponentType::Dominance), None); - - let (unnamed_dominance_components, named_dominance_componts): (Vec<_>, Vec<_>) = - existing_dominance_components - .into_iter() - .filter(|c| c != &default_dominance_component) - .partition(|c| c.name.is_empty()); - - let named_dominance_gs: Vec<_> = named_dominance_componts - .into_iter() - .filter_map(|c| graph.get_graphstorage(&c)) - .collect(); - - let unnamed_dominance_gs: Vec<_> = unnamed_dominance_components - .into_iter() - .filter_map(|c| graph.get_graphstorage(&c)) - .collect(); - - let default_dominance_gs = - graph.get_or_create_writable(&default_dominance_component)?; - for other_dominance_gs in named_dominance_gs { - for source_node in other_dominance_gs.source_nodes() { - let source = source_node?; - // The ">" operator will search all components that have an empty name. - // We don't need to add an edge if its already part of such a component. - let existing_unnamed_outgoing: Result> = unnamed_dominance_gs - .iter() - .flat_map(|gs| gs.get_outgoing_edges(source)) - .collect(); - let existing_unnamed_outgoing = - HashSet::::from_iter(existing_unnamed_outgoing?); - for target in other_dominance_gs.get_outgoing_edges(source) { - let target = target?; - if !existing_unnamed_outgoing.contains(&target) { - default_dominance_gs.add_edge(Edge { source, target })?; - } - } - } - } - } - - // TODO: delete default graph storage when empty - Ok(()) } diff --git a/graphannis/src/annis/db/aql/model/tests.rs b/graphannis/src/annis/db/aql/model/tests.rs index a1638cc74..b84ba2b51 100644 --- a/graphannis/src/annis/db/aql/model/tests.rs +++ b/graphannis/src/annis/db/aql/model/tests.rs @@ -349,7 +349,7 @@ fn add_token_to_single_sentence() { #[test] fn add_dominance_edges_with_empty_name() { let content = - &include_bytes!("../../../../../tests/single_sentence only_named_edges.graphml")[..]; + &include_bytes!("../../../../../tests/single_sentence_only_named_edges.graphml")[..]; let (graph, _config) = graphml::import::(content, false, |_| {}).unwrap(); diff --git a/graphannis/tests/single_sentence only_named_edges.graphml b/graphannis/tests/single_sentence_only_named_edges.graphml similarity index 100% rename from graphannis/tests/single_sentence only_named_edges.graphml rename to graphannis/tests/single_sentence_only_named_edges.graphml From f467758fff4876105e4b50a2a687d63604dd315d Mon Sep 17 00:00:00 2001 From: Thomas Krause Date: Wed, 14 Oct 2026 15:29:51 +0200 Subject: [PATCH 10/10] Only include non-empty graph storages for arity and base edge operator (e.g. Dominance) --- graphannis/src/annis/db/aql/operators/arity.rs | 8 ++++++-- graphannis/src/annis/db/aql/operators/edge_op.rs | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/graphannis/src/annis/db/aql/operators/arity.rs b/graphannis/src/annis/db/aql/operators/arity.rs index f935712ae..0e7f5b3b2 100644 --- a/graphannis/src/annis/db/aql/operators/arity.rs +++ b/graphannis/src/annis/db/aql/operators/arity.rs @@ -36,12 +36,16 @@ impl UnaryOperatorSpec for AritySpec { let mut graphstorages = Vec::default(); for component in db.get_all_components(Some(AnnotationComponentType::Dominance), None) { - if let Some(gs) = db.get_graphstorage(&component) { + if let Some(gs) = db.get_graphstorage(&component) + && gs.get_statistics().is_none_or(|stats| stats.nodes > 0) + { graphstorages.push(gs); } } for component in db.get_all_components(Some(AnnotationComponentType::Pointing), None) { - if let Some(gs) = db.get_graphstorage(&component) { + if let Some(gs) = db.get_graphstorage(&component) + && gs.get_statistics().is_none_or(|stats| stats.nodes > 0) + { graphstorages.push(gs); } } diff --git a/graphannis/src/annis/db/aql/operators/edge_op.rs b/graphannis/src/annis/db/aql/operators/edge_op.rs index 11a0969c4..f04c35765 100644 --- a/graphannis/src/annis/db/aql/operators/edge_op.rs +++ b/graphannis/src/annis/db/aql/operators/edge_op.rs @@ -43,7 +43,12 @@ impl BaseEdgeOp { GraphAnnisError::ImpossibleSearch(format!("Component {} does not exist", c)) })?; - gs.push(gs_for_component); + if gs_for_component + .get_statistics() + .is_none_or(|stats| stats.nodes > 0) + { + gs.push(gs_for_component); + } } let max_nodes_estimate = calculate_max_node_estimate(db, &spec, &gs, false)?;