Skip to content

Commit 0e1cdee

Browse files
authored
Timestamp field bound precision (#6201)
* Add test to reproduce the issue * Remove excessive upper bound simplification
1 parent d8188cb commit 0e1cdee

4 files changed

Lines changed: 79 additions & 25 deletions

File tree

quickwit/quickwit-search/src/leaf.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -844,28 +844,24 @@ fn remove_redundant_timestamp_range(
844844
}
845845
}
846846
(Bound::Unbounded, Some(_)) => Bound::Unbounded,
847-
(timestamp, None) => timestamp,
847+
(query_bound, None) => query_bound,
848848
};
849-
let final_end_timestamp = match (
850-
visitor.end_timestamp,
851-
split.timestamp_end.map(DateTime::from_timestamp_secs),
852-
) {
853-
(Bound::Included(query_ts), Some(split_ts)) => {
854-
if query_ts < split_ts {
855-
Bound::Included(query_ts)
856-
} else {
857-
Bound::Unbounded
858-
}
859-
}
860-
(Bound::Excluded(query_ts), Some(split_ts)) => {
861-
if query_ts <= split_ts {
862-
Bound::Excluded(query_ts)
849+
let final_end_timestamp = match (visitor.end_timestamp, split.timestamp_end) {
850+
(
851+
query_bound @ (Bound::Included(query_ts) | Bound::Excluded(query_ts)),
852+
Some(split_end),
853+
) => {
854+
// split.timestamp_end is the truncation of the highest timestamp in the split,
855+
// so the actual known bound for the split is split.timestamp_end+1 (exclusive)
856+
let split_end_exclusive = DateTime::from_timestamp_secs(split_end + 1);
857+
if query_ts < split_end_exclusive {
858+
query_bound
863859
} else {
864860
Bound::Unbounded
865861
}
866862
}
867863
(Bound::Unbounded, Some(_)) => Bound::Unbounded,
868-
(timestamp, None) => timestamp,
864+
(query_bound, None) => query_bound,
869865
};
870866
if final_start_timestamp != Bound::Unbounded || final_end_timestamp != Bound::Unbounded {
871867
let range = RangeQuery {
@@ -1998,6 +1994,11 @@ mod tests {
19981994
};
19991995
remove_timestamp_test_case(&search_request, &split, None);
20001996

1997+
let expected_upper_inclusive = RangeQuery {
1998+
field: timestamp_field.to_string(),
1999+
lower_bound: Bound::Unbounded,
2000+
upper_bound: Bound::Included((time3 * S_TO_NS).into()),
2001+
};
20012002
let search_request = SearchRequest {
20022003
query_ast: serde_json::to_string(&QueryAst::Range(RangeQuery {
20032004
field: timestamp_field.to_string(),
@@ -2007,7 +2008,7 @@ mod tests {
20072008
.unwrap(),
20082009
..SearchRequest::default()
20092010
};
2010-
remove_timestamp_test_case(&search_request, &split, None);
2011+
remove_timestamp_test_case(&search_request, &split, Some(expected_upper_inclusive));
20112012

20122013
let search_request = SearchRequest {
20132014
query_ast: serde_json::to_string(&QueryAst::MatchAll).unwrap(),
@@ -2050,10 +2051,10 @@ mod tests {
20502051
Some(expected_upper_exclusive.clone()),
20512052
);
20522053

2053-
let expected_lower_exclusive = RangeQuery {
2054+
let expected_lower_excl_upper_incl = RangeQuery {
20542055
field: timestamp_field.to_string(),
20552056
lower_bound: Bound::Excluded((time2 * S_TO_NS).into()),
2056-
upper_bound: Bound::Unbounded,
2057+
upper_bound: Bound::Included((time3 * S_TO_NS).into()),
20572058
};
20582059
let search_request = SearchRequest {
20592060
query_ast: serde_json::to_string(&QueryAst::Range(RangeQuery {
@@ -2067,10 +2068,22 @@ mod tests {
20672068
remove_timestamp_test_case(
20682069
&search_request,
20692070
&split,
2070-
Some(expected_lower_exclusive.clone()),
2071+
Some(expected_lower_excl_upper_incl.clone()),
20712072
);
2073+
}
2074+
2075+
#[test]
2076+
fn test_remove_timestamp_range_multiple_bounds() {
2077+
// When bounds are defined both in the AST and in the search request,
2078+
// make sure we take the most restrictive ones.
2079+
const S_TO_NS: i64 = 1_000_000_000;
2080+
let time1 = 1700001000;
2081+
let time2 = 1700002000;
2082+
let time3 = 1700003000;
2083+
let time4 = 1700004000;
2084+
2085+
let timestamp_field = "timestamp".to_string();
20722086

2073-
// we take the most restrictive bounds
20742087
let split = SplitIdAndFooterOffsets {
20752088
timestamp_start: Some(time1),
20762089
timestamp_end: Some(time4),
@@ -2113,10 +2126,10 @@ mod tests {
21132126
};
21142127
remove_timestamp_test_case(&search_request, &split, Some(expected_upper_2_inc));
21152128

2116-
let expected_lower_3 = RangeQuery {
2129+
let expected_lower_3_upper_4 = RangeQuery {
21172130
field: timestamp_field.to_string(),
21182131
lower_bound: Bound::Included((time3 * S_TO_NS).into()),
2119-
upper_bound: Bound::Unbounded,
2132+
upper_bound: Bound::Included((time4 * S_TO_NS).into()),
21202133
};
21212134

21222135
let search_request = SearchRequest {
@@ -2130,7 +2143,11 @@ mod tests {
21302143
end_timestamp: Some(time4 + 1),
21312144
..SearchRequest::default()
21322145
};
2133-
remove_timestamp_test_case(&search_request, &split, Some(expected_lower_3.clone()));
2146+
remove_timestamp_test_case(
2147+
&search_request,
2148+
&split,
2149+
Some(expected_lower_3_upper_4.clone()),
2150+
);
21342151

21352152
let search_request = SearchRequest {
21362153
query_ast: serde_json::to_string(&QueryAst::Range(RangeQuery {
@@ -2143,7 +2160,7 @@ mod tests {
21432160
end_timestamp: Some(time4 + 1),
21442161
..SearchRequest::default()
21452162
};
2146-
remove_timestamp_test_case(&search_request, &split, Some(expected_lower_3));
2163+
remove_timestamp_test_case(&search_request, &split, Some(expected_lower_3_upper_4));
21472164

21482165
let mut search_request = SearchRequest {
21492166
query_ast: serde_json::to_string(&QueryAst::MatchAll).unwrap(),

quickwit/rest-api-tests/scenarii/qw_search_api/0001_ts_range.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,9 @@ params:
4040
query: "auto_date:>=2023-05-25T00:00:00Z AND auto_date:<2023-05-26T00:00:00Z"
4141
expected:
4242
num_hits: 2
43+
---
44+
endpoint: millisec/search
45+
params:
46+
query: "ts:>=2022-12-16T10:00:57.000Z AND ts:<=2022-12-16T10:00:57.000Z"
47+
expected:
48+
num_hits: 1

quickwit/rest-api-tests/scenarii/qw_search_api/_setup.quickwit.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,31 @@ ndjson:
9898
- {"text_raw": "indexed with raw tokenizer dashes"}
9999
- {"text_fast": "fast-text-value-dashes"}
100100
- {"text_fast": "fast text value whitespaces"}
101+
---
102+
method: DELETE
103+
endpoint: indexes/millisec
104+
status_code: null
105+
---
106+
method: POST
107+
endpoint: indexes/
108+
json:
109+
version: "0.7"
110+
index_id: millisec
111+
doc_mapping:
112+
timestamp_field: ts
113+
mode: strict
114+
field_mappings:
115+
- name: ts
116+
type: datetime
117+
fast: true
118+
input_formats: ["rfc3339"]
119+
fast_precision: milliseconds
120+
---
121+
method: POST
122+
endpoint: millisec/ingest
123+
params:
124+
commit: force
125+
ndjson:
126+
- {"ts": "2022-12-16T10:00:56.297Z"}
127+
- {"ts": "2022-12-16T10:00:57.000Z"}
128+
- {"ts": "2022-12-16T10:00:57.297Z"}

quickwit/rest-api-tests/scenarii/qw_search_api/_teardown.quickwit.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ endpoint: indexes/simple
33
---
44
method: DELETE
55
endpoint: indexes/nested
6+
---
7+
method: DELETE
8+
endpoint: indexes/millisec

0 commit comments

Comments
 (0)