Add RegexPhraseQuery::from_regexes (#3138)

RegexQuery can be built from a compiled Regex, but RegexPhraseQuery always compiled its patterns with Regex::new and its default DFA state limit. from_regexes takes the compiled regexes directly, so a caller can build them with its own limits, or reuse ones it already compiled.
This commit is contained in:
palmoni5
2026-09-29 11:45:00 +02:00
committed by GitHub
parent 9e1b0de23a
commit 637803de99
2 changed files with 61 additions and 0 deletions
@@ -96,6 +96,36 @@ impl RegexPhraseQuery {
}
}
/// Creates a new `RegexPhraseQuery` from already compiled regexes, e.g. built with a
/// non-default state limit.
///
/// Each term is `(offset, pattern, regex)`, where `regex` is the compilation of
/// `pattern`; the pattern is what [`RegexPhraseQuery::phrase_terms`] returns.
pub fn from_regexes(
field: Field,
mut terms: Vec<(usize, String, Arc<Regex>)>,
slop: u32,
) -> RegexPhraseQuery {
assert!(
terms.len() > 1,
"A phrase query is required to have strictly more than one term."
);
terms.sort_by_key(|&(offset, _, _)| offset);
let (phrase_terms, regexes): (Vec<_>, Vec<_>) = terms
.into_iter()
.map(|(offset, pattern, regex)| ((offset, pattern), regex))
.unzip();
let compiled = OnceCell::new();
let _ = compiled.set(regexes);
RegexPhraseQuery {
field,
phrase_terms,
slop,
max_expansions: 1 << 14,
regexes: CompiledRegexes(compiled),
}
}
/// Slop allowed for the phrase.
///
/// The query will match if its terms are separated by `slop` terms at most.
@@ -382,6 +382,37 @@ mod tests {
Ok(())
}
#[test]
pub fn test_phrase_from_regexes_uses_the_given_automata() -> crate::Result<()> {
use std::sync::Arc;
use tantivy_fst::Regex;
use crate::collector::Count;
let index = create_index(&["a b", "aa b", "b a", "a c"])?;
let text_field = index.schema().get_field("text").unwrap();
let searcher = index.reader()?.searcher();
let regex_a = Arc::new(Regex::new("a.*").unwrap());
let regex_b = Arc::new(Regex::new("b").unwrap());
let from_regexes = RegexPhraseQuery::from_regexes(
text_field,
vec![
(1, "b".into(), regex_b.clone()),
(0, "a.*".into(), regex_a.clone()),
],
0,
);
let regexes = from_regexes.regexes()?;
assert!(Arc::ptr_eq(&regexes[0], &regex_a));
assert!(Arc::ptr_eq(&regexes[1], &regex_b));
let from_patterns = RegexPhraseQuery::new(text_field, vec!["a.*".into(), "b".into()]);
assert_eq!(searcher.search(&from_regexes, &Count)?, 2);
assert_eq!(searcher.search(&from_patterns, &Count)?, 2);
Ok(())
}
#[test]
pub fn test_phrase_count() -> crate::Result<()> {
let index = create_index(&["a c", "a a b d a b c", " a b"])?;