Skip to content

Commit 24a0afe

Browse files
committed
feat(fields): filter field with "contains" behavior
1 parent 782f7fc commit 24a0afe

3 files changed

Lines changed: 62 additions & 20 deletions

File tree

quickwit/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ sasl2-sys = { git = "https://github.com/quickwit-oss/rust-sasl/", rev = "085a4c7
394394
#tracing-subscriber = { git = "https://github.com/trinity-1686a/tracing.git", rev = "6806cac3" }
395395

396396
[profile.dev]
397-
debug = false
397+
debug = true
398398

399399
[profile.release]
400400
lto = "thin"

quickwit/quickwit-search/src/list_fields.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,25 +279,37 @@ fn matches_any_pattern(field_name: &str, field_patterns: &[FieldPattern]) -> boo
279279
enum FieldPattern {
280280
Match { field: String },
281281
Wildcard { prefix: String, suffix: String },
282+
Contains { infix: String },
282283
}
283284

284285
impl FromStr for FieldPattern {
285286
type Err = crate::SearchError;
286287

287288
fn from_str(field_pattern: &str) -> crate::Result<Self> {
288-
match field_pattern.find('*') {
289+
if field_pattern.starts_with('*') && field_pattern.ends_with('*') {
290+
let infix = field_pattern.trim_matches('*').to_string();
291+
if infix.contains('*') {
292+
return Err(crate::SearchError::InvalidArgument(format!(
293+
"invalid field pattern `{field_pattern}`: 'contains' type patterns can't have a wildcard in the middle"
294+
)));
295+
}
296+
return Ok(Self::Contains { infix });
297+
}
298+
299+
match field_pattern.split_once('*') {
289300
None => Ok(FieldPattern::Match {
290301
field: field_pattern.to_string(),
291302
}),
292-
Some(pos) => {
293-
let prefix = field_pattern[..pos].to_string();
294-
let suffix = field_pattern[pos + 1..].to_string();
303+
Some((prefix, suffix)) => {
295304
if suffix.contains("*") {
296305
return Err(crate::SearchError::InvalidArgument(format!(
297306
"invalid field pattern `{field_pattern}`: we only support one wildcard"
298307
)));
299308
}
300-
Ok(FieldPattern::Wildcard { prefix, suffix })
309+
Ok(FieldPattern::Wildcard {
310+
prefix: prefix.to_string(),
311+
suffix: suffix.to_string(),
312+
})
301313
}
302314
}
303315
}
@@ -310,6 +322,7 @@ impl FieldPattern {
310322
FieldPattern::Wildcard { prefix, suffix } => {
311323
field_name.starts_with(prefix) && field_name.ends_with(suffix)
312324
}
325+
FieldPattern::Contains { infix } => field_name.contains(infix),
313326
}
314327
}
315328
}
@@ -331,7 +344,7 @@ pub async fn leaf_list_fields(
331344

332345
// If no splits, return empty response
333346
if split_ids.is_empty() {
334-
return Ok(ListFieldsResponse { fields: Vec::new() });
347+
return Ok(ListFieldsResponse::default());
335348
}
336349

337350
// Get fields from all splits
@@ -1032,6 +1045,15 @@ mod tests {
10321045
assert!(!inner_pattern.matches("tito"));
10331046
assert!(inner_pattern.matches("towhateverti"));
10341047

1048+
let contains_pattern = FieldPattern::from_str("*my_field*").unwrap();
1049+
assert!(!contains_pattern.matches(""));
1050+
assert!(!contains_pattern.matches("my_fiel"));
1051+
assert!(contains_pattern.matches("my_field"));
1052+
assert!(contains_pattern.matches("prefix_my_field"));
1053+
assert!(contains_pattern.matches("my_field_suffix"));
1054+
assert!(contains_pattern.matches("prefix_my_field_suffix"));
1055+
10351056
assert!(FieldPattern::from_str("to**").is_err());
1057+
assert!(FieldPattern::from_str("*a*b*").is_err());
10361058
}
10371059
}

quickwit/quickwit-serve/src/search_api/rest_handler.rs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,38 @@ pub struct ListFieldsQueryString {
271271
/// The results with rank [start_offset..start_offset + max_hits) are returned
272272
#[serde(default)] // Default to 0. (We are 0-indexed)
273273
pub start_offset: u64,
274+
/// Query text. The query language is that of tantivy.
275+
#[serde(default)]
276+
pub query: String,
274277
/// The output format.
275278
#[serde(default)]
276279
pub format: BodyFormat,
277280
}
278281

282+
pub fn list_fields_request_from_api_request(
283+
index_id_patterns: Vec<String>,
284+
request: ListFieldsQueryString,
285+
) -> Result<ListFieldsRequest, SearchError> {
286+
let query_ast = query_ast_from_user_text(&request.query, None);
287+
let query_ast = serde_json::to_string(&query_ast)?;
288+
289+
let req = ListFieldsRequest {
290+
end_timestamp: request.end_timestamp,
291+
start_timestamp: request.start_timestamp,
292+
fields: if !request.fields.is_empty() {
293+
request.fields.split(',').map(String::from).collect()
294+
} else {
295+
Default::default()
296+
},
297+
index_id_patterns,
298+
max_fields: request.max_fields,
299+
start_offset: request.start_offset,
300+
query_ast: Some(query_ast),
301+
};
302+
303+
Ok(req)
304+
}
305+
279306
pub fn search_request_from_api_request(
280307
index_id_patterns: Vec<String>,
281308
search_request: SearchRequestQueryString,
@@ -404,19 +431,12 @@ async fn list_fields(
404431
request: ListFieldsQueryString,
405432
search_service: Arc<dyn SearchService>,
406433
) -> impl warp::Reply {
407-
let req = ListFieldsRequest {
408-
end_timestamp: request.end_timestamp,
409-
start_timestamp: request.start_timestamp,
410-
fields: if !request.fields.is_empty() {
411-
request.fields.split(',').map(String::from).collect()
412-
} else {
413-
Default::default()
414-
},
415-
index_id_patterns,
416-
max_fields: request.max_fields,
417-
start_offset: request.start_offset,
418-
};
419-
let result = search_service.root_list_fields(req).await;
434+
let result: Result<_, SearchError> = async {
435+
let request = list_fields_request_from_api_request(index_id_patterns, request)?;
436+
search_service.root_list_fields(request).await
437+
}
438+
.await;
439+
420440
into_rest_api_response(result, BodyFormat::Json)
421441
}
422442

0 commit comments

Comments
 (0)