Skip to main content

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}