nautilus_persistence/backend/parquet/intervals.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Closed-interval set operations used by the parquet catalog for coverage and gap analysis.
17
18use nautilus_core::ClosedInterval;
19
20use crate::common::coverage::missing_intervals;
21
22/// Checks if a list of closed integer intervals are all mutually disjoint.
23///
24/// Returns `true` for empty lists or lists with a single interval.
25#[must_use]
26pub fn are_intervals_disjoint(intervals: &[(u64, u64)]) -> bool {
27 let n = intervals.len();
28
29 if n <= 1 {
30 return true;
31 }
32
33 let mut sorted_intervals: Vec<(u64, u64)> = intervals.to_vec();
34 sorted_intervals.sort_by_key(|&(start, _)| start);
35
36 for i in 0..(n - 1) {
37 let (_, end1) = sorted_intervals[i];
38 let (start2, _) = sorted_intervals[i + 1];
39
40 if end1 >= start2 {
41 return false;
42 }
43 }
44
45 true
46}
47
48/// Checks if intervals are contiguous (adjacent with no gaps).
49///
50/// Intervals are contiguous if, when sorted by start time, each interval's start
51/// timestamp is exactly one more than the previous interval's end timestamp.
52#[must_use]
53pub fn are_intervals_contiguous(intervals: &[(u64, u64)]) -> bool {
54 let n = intervals.len();
55 if n <= 1 {
56 return true;
57 }
58
59 let mut sorted_intervals: Vec<(u64, u64)> = intervals.to_vec();
60 sorted_intervals.sort_by_key(|&(start, _)| start);
61
62 for i in 0..(n - 1) {
63 let (_, end1) = sorted_intervals[i];
64 let (start2, _) = sorted_intervals[i + 1];
65
66 if end1 + 1 != start2 {
67 return false;
68 }
69 }
70
71 true
72}
73
74/// Finds the parts of a query interval that are not covered by existing data intervals.
75///
76/// Returns a vector of (start, end) tuples representing the gaps in coverage.
77pub(crate) fn query_interval_diff(
78 start: u64,
79 end: u64,
80 closed_intervals: &[(u64, u64)],
81) -> Vec<(u64, u64)> {
82 if start > end {
83 return Vec::new();
84 }
85
86 let intervals = closed_intervals
87 .iter()
88 .filter_map(|&(start, end)| ClosedInterval::new(start, end))
89 .collect::<Vec<_>>();
90
91 missing_intervals(start, end, &intervals)
92 .into_iter()
93 .map(Into::into)
94 .collect()
95}