Skip to content

Commit bbab186

Browse files
committed
fix(query-parser): honor phrase prefix and slop on JSON fields
Phrase prefix (`"..."*`) and slop (`"..."~N`) were silently dropped on JSON fields. `generate_literals_for_json_object` hard-coded `slop: 0` and `prefix: false`, so a query like `data.name:"foo bar"*` degraded to an exact phrase, even though the underlying `PhrasePrefixQuery` already supports JSON-path terms. Thread `slop`/`prefix` through the JSON branch, exactly as the `Str` branch already does, and add the "phrase prefix requires at least two terms" guard for the single-token case (mirroring `generate_literals_for_str`). Tests added as JSON analogues of the existing text-field tests: - test_phrase_prefix_on_json_field - test_phrase_prefix_too_short_on_json_field - test_phrase_slop_on_json_field This aligns JSON fields with the phrase `~`/`*` behavior already documented for `QueryParser`.
1 parent 1e859fd commit bbab186

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Tantivy 0.26 (Unreleased)
1717
- Fix integer overflow in segment sorting and merge policy truncation [#2846](https://github.com/quickwit-oss/tantivy/pull/2846)(@anaslimem)
1818
- Fix merging of intermediate aggregation results [#2719](https://github.com/quickwit-oss/tantivy/pull/2719)(@PSeitz)
1919
- Fix deduplicate doc counts in term aggregation for multi-valued fields [#2854](https://github.com/quickwit-oss/tantivy/pull/2854)(@nuri-yoo)
20+
- Honor phrase prefix (`"..."*`) and slop (`"..."~N`) on JSON fields; previously both were silently dropped, degrading the query to an exact phrase [#XXXX](https://github.com/quickwit-oss/tantivy/pull/XXXX)(@DavIvek)
2021

2122
## Features/Improvements
2223
- **Aggregation**

src/query/query_parser/query_parser.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,8 @@ impl QueryParser {
602602
field,
603603
json_path,
604604
phrase,
605+
slop,
606+
prefix,
605607
&self.tokenizer_manager,
606608
json_options,
607609
),
@@ -995,11 +997,14 @@ fn generate_literals_for_str(
995997
}))
996998
}
997999

1000+
#[expect(clippy::too_many_arguments)]
9981001
fn generate_literals_for_json_object(
9991002
field_name: &str,
10001003
field: Field,
10011004
json_path: &str,
10021005
phrase: &str,
1006+
slop: u32,
1007+
prefix: bool,
10031008
tokenizer_manager: &TokenizerManager,
10041009
json_options: &JsonObjectOptions,
10051010
) -> Result<Vec<LogicalLiteral>, QueryParserError> {
@@ -1036,6 +1041,12 @@ fn generate_literals_for_json_object(
10361041
});
10371042

10381043
if positions_and_terms.len() <= 1 {
1044+
if prefix {
1045+
return Err(QueryParserError::PhrasePrefixRequiresAtLeastTwoTerms {
1046+
phrase: phrase.to_owned(),
1047+
tokenizer: text_options.tokenizer().to_owned(),
1048+
});
1049+
}
10391050
for (_, term) in positions_and_terms {
10401051
logical_literals.push(LogicalLiteral::Term(term));
10411052
}
@@ -1048,8 +1059,8 @@ fn generate_literals_for_json_object(
10481059
}
10491060
logical_literals.push(LogicalLiteral::Phrase {
10501061
terms: positions_and_terms,
1051-
slop: 0,
1052-
prefix: false,
1062+
slop,
1063+
prefix,
10531064
});
10541065
Ok(logical_literals)
10551066
}
@@ -1963,6 +1974,42 @@ mod test {
19631974
);
19641975
}
19651976

1977+
#[test]
1978+
pub fn test_phrase_prefix_on_json_field() {
1979+
let query_parser = make_query_parser();
1980+
let query = query_parser
1981+
.parse_query("json.attr:\"big bad wo\"*")
1982+
.unwrap();
1983+
assert_eq!(
1984+
format!("{query:?}"),
1985+
"PhrasePrefixQuery { field: Field(14), phrase_terms: [(0, Term(field=14, type=Json, \
1986+
path=attr, type=Str, \"big\")), (1, Term(field=14, type=Json, path=attr, type=Str, \
1987+
\"bad\"))], prefix: (2, Term(field=14, type=Json, path=attr, type=Str, \"wo\")), \
1988+
max_expansions: 50 }"
1989+
);
1990+
}
1991+
1992+
#[test]
1993+
pub fn test_phrase_prefix_too_short_on_json_field() {
1994+
let err = parse_query_to_logical_ast("json.attr:\"wo\"*", true).unwrap_err();
1995+
assert_eq!(
1996+
err,
1997+
QueryParserError::PhrasePrefixRequiresAtLeastTwoTerms {
1998+
phrase: "wo".to_owned(),
1999+
tokenizer: "default".to_owned()
2000+
}
2001+
);
2002+
}
2003+
2004+
#[test]
2005+
pub fn test_phrase_slop_on_json_field() {
2006+
test_parse_query_to_logical_ast_helper(
2007+
"json.attr:\"a b\"~2",
2008+
r#""[(0, Term(field=14, type=Json, path=attr, type=Str, "a")), (1, Term(field=14, type=Json, path=attr, type=Str, "b"))]"~2"#,
2009+
false,
2010+
);
2011+
}
2012+
19662013
#[test]
19672014
pub fn test_term_set_query() {
19682015
test_parse_query_to_logical_ast_helper(

0 commit comments

Comments
 (0)