common_time/
ttl.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
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
15use std::fmt::Display;
16use std::time::Duration;
17
18use serde::{Deserialize, Serialize};
19use snafu::ResultExt;
20
21use crate::error::{Error, InvalidDatabaseTtlSnafu, ParseDurationSnafu};
22use crate::Timestamp;
23
24pub const INSTANT: &str = "instant";
25pub const FOREVER: &str = "forever";
26
27/// Time To Live for database, which can be `Forever`, or a `Duration`, but can't be `Instant`.
28///
29/// unlike `TimeToLive` which can be `Instant`, `Forever`, or a `Duration`
30#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum DatabaseTimeToLive {
33    /// Keep the data forever
34    #[default]
35    Forever,
36    /// Duration to keep the data, this duration should be non-zero
37    #[serde(untagged, with = "humantime_serde")]
38    Duration(Duration),
39}
40
41impl Display for DatabaseTimeToLive {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            DatabaseTimeToLive::Forever => write!(f, "{}", FOREVER),
45            DatabaseTimeToLive::Duration(d) => write!(f, "{}", humantime::Duration::from(*d)),
46        }
47    }
48}
49
50impl DatabaseTimeToLive {
51    /// Parse a string that is either `forever`, or a duration to `TimeToLive`
52    ///
53    /// note that an empty string or a zero duration(a duration that spans no time) is treat as `forever` too
54    pub fn from_humantime_or_str(s: &str) -> Result<Self, Error> {
55        let ttl = match s.to_lowercase().as_ref() {
56            INSTANT => InvalidDatabaseTtlSnafu.fail()?,
57            FOREVER | "" => Self::Forever,
58            _ => {
59                let d = humantime::parse_duration(s).context(ParseDurationSnafu)?;
60                Self::from(d)
61            }
62        };
63        Ok(ttl)
64    }
65}
66
67impl TryFrom<TimeToLive> for DatabaseTimeToLive {
68    type Error = Error;
69    fn try_from(value: TimeToLive) -> Result<Self, Self::Error> {
70        match value {
71            TimeToLive::Instant => InvalidDatabaseTtlSnafu.fail()?,
72            TimeToLive::Forever => Ok(Self::Forever),
73            TimeToLive::Duration(d) => Ok(Self::from(d)),
74        }
75    }
76}
77
78impl From<DatabaseTimeToLive> for TimeToLive {
79    fn from(value: DatabaseTimeToLive) -> Self {
80        match value {
81            DatabaseTimeToLive::Forever => TimeToLive::Forever,
82            DatabaseTimeToLive::Duration(d) => TimeToLive::from(d),
83        }
84    }
85}
86
87impl From<Duration> for DatabaseTimeToLive {
88    fn from(duration: Duration) -> Self {
89        if duration.is_zero() {
90            Self::Forever
91        } else {
92            Self::Duration(duration)
93        }
94    }
95}
96
97impl From<humantime::Duration> for DatabaseTimeToLive {
98    fn from(duration: humantime::Duration) -> Self {
99        Self::from(*duration)
100    }
101}
102
103/// Time To Live
104#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum TimeToLive {
107    /// Instantly discard upon insert
108    Instant,
109    /// Keep the data forever
110    #[default]
111    Forever,
112    /// Duration to keep the data, this duration should be non-zero
113    #[serde(untagged, with = "humantime_serde")]
114    Duration(Duration),
115}
116
117impl Display for TimeToLive {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            TimeToLive::Instant => write!(f, "{}", INSTANT),
121            TimeToLive::Duration(d) => write!(f, "{}", humantime::Duration::from(*d)),
122            TimeToLive::Forever => write!(f, "{}", FOREVER),
123        }
124    }
125}
126
127impl TimeToLive {
128    /// Parse a string that is either `instant`, `forever`, or a duration to `TimeToLive`
129    ///
130    /// note that an empty string or a zero duration(a duration that spans no time) is treat as `forever` too
131    pub fn from_humantime_or_str(s: &str) -> Result<Self, Error> {
132        match s.to_lowercase().as_ref() {
133            INSTANT => Ok(TimeToLive::Instant),
134            FOREVER | "" => Ok(TimeToLive::Forever),
135            _ => {
136                let d = humantime::parse_duration(s).context(ParseDurationSnafu)?;
137                Ok(TimeToLive::from(d))
138            }
139        }
140    }
141
142    /// Check if the TimeToLive is expired
143    /// with the given `created_at` and `now` timestamp
144    pub fn is_expired(
145        &self,
146        created_at: &Timestamp,
147        now: &Timestamp,
148    ) -> crate::error::Result<bool> {
149        Ok(match self {
150            TimeToLive::Instant => true,
151            TimeToLive::Forever => false,
152            TimeToLive::Duration(d) => now.sub_duration(*d)? > *created_at,
153        })
154    }
155
156    /// is instant variant
157    pub fn is_instant(&self) -> bool {
158        matches!(self, TimeToLive::Instant)
159    }
160
161    /// Is the default value, which is `Forever`
162    pub fn is_forever(&self) -> bool {
163        matches!(self, TimeToLive::Forever)
164    }
165}
166
167impl From<Duration> for TimeToLive {
168    fn from(duration: Duration) -> Self {
169        if duration.is_zero() {
170            // compatibility with old code, and inline with cassandra's behavior when ttl set to 0
171            TimeToLive::Forever
172        } else {
173            TimeToLive::Duration(duration)
174        }
175    }
176}
177
178impl From<humantime::Duration> for TimeToLive {
179    fn from(duration: humantime::Duration) -> Self {
180        Self::from(*duration)
181    }
182}
183
184#[cfg(test)]
185mod test {
186    use super::*;
187
188    #[test]
189    fn test_db_ttl_table_ttl() {
190        // test from ttl to db ttl
191        let ttl = TimeToLive::from(Duration::from_secs(10));
192        let db_ttl: DatabaseTimeToLive = ttl.try_into().unwrap();
193        assert_eq!(db_ttl, DatabaseTimeToLive::from(Duration::from_secs(10)));
194        assert_eq!(TimeToLive::from(db_ttl), ttl);
195
196        let ttl = TimeToLive::from(Duration::from_secs(0));
197        let db_ttl: DatabaseTimeToLive = ttl.try_into().unwrap();
198        assert_eq!(db_ttl, DatabaseTimeToLive::Forever);
199        assert_eq!(TimeToLive::from(db_ttl), ttl);
200
201        let ttl = TimeToLive::Instant;
202        let err_instant = DatabaseTimeToLive::try_from(ttl);
203        assert!(err_instant.is_err());
204
205        // test 0 duration
206        let ttl = Duration::from_secs(0);
207        let db_ttl: DatabaseTimeToLive = ttl.into();
208        assert_eq!(db_ttl, DatabaseTimeToLive::Forever);
209
210        let ttl = Duration::from_secs(10);
211        let db_ttl: DatabaseTimeToLive = ttl.into();
212        assert_eq!(
213            db_ttl,
214            DatabaseTimeToLive::Duration(Duration::from_secs(10))
215        );
216
217        let ttl = DatabaseTimeToLive::from_humantime_or_str("10s").unwrap();
218        let ttl: TimeToLive = ttl.into();
219        assert_eq!(ttl, TimeToLive::from(Duration::from_secs(10)));
220
221        let ttl = DatabaseTimeToLive::from_humantime_or_str("forever").unwrap();
222        let ttl: TimeToLive = ttl.into();
223        assert_eq!(ttl, TimeToLive::Forever);
224
225        assert!(DatabaseTimeToLive::from_humantime_or_str("instant").is_err());
226
227        // test 0s
228        let ttl = DatabaseTimeToLive::from_humantime_or_str("0s").unwrap();
229        let ttl: TimeToLive = ttl.into();
230        assert_eq!(ttl, TimeToLive::Forever);
231    }
232
233    #[test]
234    fn test_serde() {
235        let cases = vec![
236            ("\"instant\"", TimeToLive::Instant),
237            ("\"forever\"", TimeToLive::Forever),
238            ("\"10d\"", Duration::from_secs(86400 * 10).into()),
239            (
240                "\"10000 years\"",
241                humantime::parse_duration("10000 years").unwrap().into(),
242            ),
243        ];
244
245        for (s, expected) in cases {
246            let serialized = serde_json::to_string(&expected).unwrap();
247            let deserialized: TimeToLive = serde_json::from_str(&serialized).unwrap();
248            assert_eq!(deserialized, expected);
249
250            let deserialized: TimeToLive = serde_json::from_str(s).unwrap_or_else(|err| {
251                panic!("Actual serialized: {}, s=`{s}`, err: {:?}", serialized, err)
252            });
253            assert_eq!(deserialized, expected);
254
255            // test db ttl too
256            if s == "\"instant\"" {
257                assert!(serde_json::from_str::<DatabaseTimeToLive>(s).is_err());
258                continue;
259            }
260
261            let db_ttl: DatabaseTimeToLive = serde_json::from_str(s).unwrap();
262            let re_serialized = serde_json::to_string(&db_ttl).unwrap();
263            assert_eq!(re_serialized, serialized);
264        }
265    }
266}