Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6ad3619
Rewrite EQ(REGEXP_EXTRACT) predicates to RegexQuery when eligible
Mallets Sep 23, 2026
0254e93
Allow unanchored EQ(REGEXP_EXTRACT) rewrite with swapped EQ args
Mallets Sep 24, 2026
aeacb30
Prefilter EQ(REGEXP_EXTRACT) with FST RegexQuery, keep exact JIT
Mallets Sep 24, 2026
ca94a53
Skip JIT for fully anchored EQ(REGEXP_EXTRACT) rewrites
Mallets Sep 24, 2026
4232fd1
Always prefilter EQ(REGEXP_EXTRACT) with FST RegexQuery
Mallets Sep 24, 2026
f187cbd
Parse REGEXP_EXTRACT pattern with regex-syntax for prefilter rewrite
Mallets Sep 25, 2026
f605ebb
Fix soundness of EQ(REGEXP_EXTRACT) FST prefilter
Mallets Sep 25, 2026
05396a2
Evaluate EQ(REGEXP_EXTRACT) on the fast-field dictionary with exact J…
Mallets Sep 25, 2026
aaee82c
Simplify RegexExtractEqQuery to a first-value document loop
Mallets Sep 25, 2026
e15e411
Read postings for EQ(REGEXP_EXTRACT) on raw-indexed fields
Mallets Sep 25, 2026
ad370b1
Skip the first-value check on single-valued postings of EQ(REGEXP_EXT…
Mallets Sep 25, 2026
6beb310
Merge remote-tracking branch 'origin/main' into mallets/regex-query-eq
Mallets Sep 25, 2026
c04994d
Keep tantivy-common on the same rev as tantivy
Mallets Sep 25, 2026
67eabe4
Walk only the term dictionary when it holds every fast-field value
Mallets Sep 25, 2026
4654ce2
Support any capture index in REGEXP_EXTRACT equality
Mallets Sep 28, 2026
96e047e
Clarify regex extract query documentation
Mallets Sep 28, 2026
954fba3
Clarify and simplify regex extract planning
Mallets Sep 28, 2026
d7b224a
Rewrite REGEXP_EXTRACT capture isolation via regex-syntax AST
Mallets Sep 28, 2026
98d99cc
Simplify verbose-flag wrapping in capture substitution
Mallets Sep 28, 2026
5643314
Cache REGEXP_EXTRACT query regexes
Mallets Sep 28, 2026
70239c6
Remove unused search FST dependency
Mallets Sep 28, 2026
4d1c7aa
Merge branch 'main' into mallets/regex-query-eq
Mallets Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion quickwit/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions quickwit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ tantivy = { git = "https://github.com/quickwit-oss/tantivy/", rev = "e229de6db74
"zstd-compression",
"columnar-zstd-compression",
] }
# Must stay on the same rev as `tantivy`.
tantivy-common = { git = "https://github.com/quickwit-oss/tantivy/", rev = "e229de6db748c6b21747a99a18220446e76104e1" }
tantivy-fst = "0.5"

# This is actually not used directly the goal is to fix the version
Expand Down
79 changes: 76 additions & 3 deletions quickwit/quickwit-doc-mapper/src/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,18 @@ impl<'a, 'b: 'a> QueryAstVisitor<'a> for ExtractPrefixTermRanges<'b> {
);
Ok(())
}

fn visit_calc_field(&mut self, calc_field_query: &'a CalcFieldQuery) -> Result<(), Self::Err> {
let Some(regex_query) = calc_field_query.try_prefilter_regex_query(self.schema) else {
return Ok(());
};
let resolved = regex_query.to_resolved(self.schema, Some(self.tokenizer_manager))?;
self.add_automaton(
resolved.field,
Automaton::Regex(resolved.json_path, resolved.regex),
);
Ok(())
}
}

type TermRangeWarmupInfo = HashMap<Field, HashMap<TermRange, PositionNeeded>>;
Expand All @@ -400,7 +412,7 @@ fn extract_prefix_term_ranges_and_automaton(

#[cfg(test)]
mod test {
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::ops::Bound;

use quickwit_common::shared_consts::FIELD_PRESENCE_FIELD_NAME;
Expand All @@ -413,11 +425,14 @@ mod test {
};
use tantivy::Term;
use tantivy::jitexpr::ast::deserialize;
use tantivy::schema::{DateOptions, DateTimePrecision, FAST, INDEXED, STORED, Schema, TEXT};
use tantivy::schema::{
DateOptions, DateTimePrecision, FAST, Field, INDEXED, STORED, STRING, Schema, TEXT,
};

use super::{ExtractPrefixTermRanges, build_query};
use crate::{
DYNAMIC_FIELD_NAME, FastFieldWarmupInfo, SOURCE_FIELD_NAME, TermRange, WarmupInfo,
Automaton, DYNAMIC_FIELD_NAME, FastFieldWarmupInfo, SOURCE_FIELD_NAME, TermRange,
WarmupInfo,
};

fn calc_field(expression: &str) -> QueryAst {
Expand Down Expand Up @@ -488,6 +503,64 @@ mod test {
);
}

#[test]
fn test_calc_field_regex_extract_eq_warmup() {
let mut schema_builder = Schema::builder();
let service = schema_builder.add_text_field("service", STRING | FAST);
schema_builder.add_text_field("tokenized", TEXT | FAST);
schema_builder.add_text_field("fast_only", FAST);
let schema = schema_builder.build();
let context = BuildTantivyAstContext::for_test(&schema);

// The predicate always reads the fast field. It may also read postings of terms accepted
// by the prefilter when the field uses the raw tokenizer for both indexing and fast fields.
for (expression, fast_field, expected_prefilter) in [
(
r#"(EQ (REGEXP_EXTRACT service "^svc-([a-z]+)-prod$" 1u64) "api")"#,
"service",
Some("svc-api-prod"),
),
(
r#"(EQ (REGEXP_EXTRACT service "svc-([a-z]+)-prod" 1u64) "api")"#,
"service",
Some("(?s:.*)svc-api-prod(?s:.*)"),
),
(
r#"(EQ (REGEXP_EXTRACT tokenized "^svc-([a-z]+)-prod$" 1u64) "api")"#,
"tokenized",
None,
),
(
r#"(EQ (REGEXP_EXTRACT fast_only "^svc-([a-z]+)-prod$" 1u64) "api")"#,
"fast_only",
None,
),
(
r#"(EQ (REGEXP_EXTRACT service "(?m)^svc-([a-z]+)" 1u64) "api")"#,
"service",
None,
),
] {
let (_, warmup) = build_query(calc_field(expression), &context, None).unwrap();
assert_eq!(
warmup.fast_fields,
expected_fast_fields(&[fast_field]),
"{expression}"
);
let expected_automatons: HashMap<Field, HashSet<Automaton>> = expected_prefilter
.map(|regex| {
let automaton = Automaton::Regex(None, regex.to_string());
(service, HashSet::from([automaton]))
})
.into_iter()
.collect();
assert_eq!(
warmup.automatons_grouped_by_field, expected_automatons,
"{expression}"
);
}
}

fn full_text_query_for_warmup(field: &str, tokenizer: Option<&str>) -> QueryAst {
quickwit_query::query_ast::FullTextQuery {
field: field.to_string(),
Expand Down
3 changes: 3 additions & 0 deletions quickwit/quickwit-query/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ anyhow = { workspace = true }
base64 = { workspace = true }
bitpacking = { workspace = true }
hex = { workspace = true }
lru = { workspace = true }
regex = { workspace = true }
regex-syntax = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
tantivy = { workspace = true }
tantivy-common = { workspace = true }
unicode-segmentation = { workspace = true }
tantivy-fst = { workspace = true }
tracing = { workspace = true }
Expand Down
4 changes: 3 additions & 1 deletion quickwit/quickwit-query/benches/tokenizers_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use criterion::{Criterion, Throughput, black_box, criterion_group, criterion_main};
use std::hint::black_box;

use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use quickwit_query::CodeTokenizer;
use tantivy::tokenizer::{RegexTokenizer, TextAnalyzer, Token, TokenStream};

Expand Down
Loading
Loading