databend_driver_core/value/
interval.rs

1// Copyright 2021 Datafuse Labs
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, Formatter};
16
17use crate::error::{Error, Result};
18
19#[derive(Debug, Copy, Clone, PartialEq, Default)]
20pub struct Interval {
21    pub months: i32,
22    pub days: i32,
23    pub micros: i64,
24}
25
26impl Display for Interval {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        let mut buffer = [0u8; 70];
29        let len = IntervalToStringCast::format(*self, &mut buffer);
30        write!(f, "{}", String::from_utf8_lossy(&buffer[..len]))
31    }
32}
33
34struct IntervalToStringCast;
35
36impl IntervalToStringCast {
37    fn format_signed_number(value: i64, buffer: &mut [u8], length: &mut usize) {
38        let s = value.to_string();
39        let bytes = s.as_bytes();
40        buffer[*length..*length + bytes.len()].copy_from_slice(bytes);
41        *length += bytes.len();
42    }
43
44    fn format_two_digits(value: i64, buffer: &mut [u8], length: &mut usize) {
45        let s = format!("{:02}", value.abs());
46        let bytes = s.as_bytes();
47        buffer[*length..*length + bytes.len()].copy_from_slice(bytes);
48        *length += bytes.len();
49    }
50
51    fn format_interval_value(value: i32, buffer: &mut [u8], length: &mut usize, name: &str) {
52        if value == 0 {
53            return;
54        }
55        if *length != 0 {
56            buffer[*length] = b' ';
57            *length += 1;
58        }
59        Self::format_signed_number(value as i64, buffer, length);
60        let name_bytes = name.as_bytes();
61        buffer[*length..*length + name_bytes.len()].copy_from_slice(name_bytes);
62        *length += name_bytes.len();
63        if value != 1 && value != -1 {
64            buffer[*length] = b's';
65            *length += 1;
66        }
67    }
68
69    fn format_micros(mut micros: i64, buffer: &mut [u8], length: &mut usize) {
70        if micros < 0 {
71            micros = -micros;
72        }
73        let s = format!("{micros:06}");
74        let bytes = s.as_bytes();
75        buffer[*length..*length + bytes.len()].copy_from_slice(bytes);
76        *length += bytes.len();
77
78        while *length > 0 && buffer[*length - 1] == b'0' {
79            *length -= 1;
80        }
81    }
82
83    pub fn format(interval: Interval, buffer: &mut [u8]) -> usize {
84        let mut length = 0;
85        if interval.months != 0 {
86            let years = interval.months / 12;
87            let months = interval.months - years * 12;
88            Self::format_interval_value(years, buffer, &mut length, " year");
89            Self::format_interval_value(months, buffer, &mut length, " month");
90        }
91        if interval.days != 0 {
92            Self::format_interval_value(interval.days, buffer, &mut length, " day");
93        }
94        if interval.micros != 0 {
95            if length != 0 {
96                buffer[length] = b' ';
97                length += 1;
98            }
99            let mut micros = interval.micros;
100            if micros < 0 {
101                buffer[length] = b'-';
102                length += 1;
103                micros = -micros;
104            }
105            let hour = micros / MINROS_PER_HOUR;
106            micros -= hour * MINROS_PER_HOUR;
107            let min = micros / MICROS_PER_MINUTE;
108            micros -= min * MICROS_PER_MINUTE;
109            let sec = micros / MICROS_PER_SEC;
110            micros -= sec * MICROS_PER_SEC;
111
112            Self::format_signed_number(hour, buffer, &mut length);
113            buffer[length] = b':';
114            length += 1;
115            Self::format_two_digits(min, buffer, &mut length);
116            buffer[length] = b':';
117            length += 1;
118            Self::format_two_digits(sec, buffer, &mut length);
119            if micros != 0 {
120                buffer[length] = b'.';
121                length += 1;
122                Self::format_micros(micros, buffer, &mut length);
123            }
124        } else if length == 0 {
125            buffer[..8].copy_from_slice(b"00:00:00");
126            return 8;
127        }
128        length
129    }
130}
131
132impl Interval {
133    pub fn from_string(str: &str) -> Result<Self> {
134        Self::from_cstring(str.as_bytes())
135    }
136
137    pub fn from_cstring(str: &[u8]) -> Result<Self> {
138        let mut result = Interval::default();
139        let mut pos = 0;
140        let len = str.len();
141        let mut found_any = false;
142
143        if len == 0 {
144            return Err(Error::BadArgument("Empty string".to_string()));
145        }
146        match str[pos] {
147            b'@' => {
148                pos += 1;
149            }
150            b'P' | b'p' => {
151                return Err(Error::BadArgument(
152                    "Posix intervals not supported yet".to_string(),
153                ));
154            }
155            _ => {}
156        }
157
158        while pos < len {
159            match str[pos] {
160                b' ' | b'\t' | b'\n' => {
161                    pos += 1;
162                    continue;
163                }
164                b'0'..=b'9' => {
165                    let (number, fraction, next_pos) = parse_number(&str[pos..])?;
166                    pos += next_pos;
167                    let (specifier, next_pos) = parse_identifier(&str[pos..]);
168
169                    pos += next_pos;
170                    let _ = apply_specifier(&mut result, number, fraction, &specifier);
171                    found_any = true;
172                }
173                b'-' => {
174                    pos += 1;
175                    let (number, fraction, next_pos) = parse_number(&str[pos..])?;
176                    let number = -number;
177                    let fraction = -fraction;
178
179                    pos += next_pos;
180
181                    let (specifier, next_pos) = parse_identifier(&str[pos..]);
182
183                    pos += next_pos;
184                    let _ = apply_specifier(&mut result, number, fraction, &specifier);
185                    found_any = true;
186                }
187                b'a' | b'A' => {
188                    if len - pos < 3
189                        || str[pos + 1] != b'g' && str[pos + 1] != b'G'
190                        || str[pos + 2] != b'o' && str[pos + 2] != b'O'
191                    {
192                        return Err(Error::BadArgument("Invalid 'ago' specifier".to_string()));
193                    }
194                    pos += 3;
195                    while pos < len {
196                        match str[pos] {
197                            b' ' | b'\t' | b'\n' => {
198                                pos += 1;
199                            }
200                            _ => {
201                                return Err(Error::BadArgument(
202                                    "Trailing characters after 'ago'".to_string(),
203                                ));
204                            }
205                        }
206                    }
207                    result.months = -result.months;
208                    result.days = -result.days;
209                    result.micros = -result.micros;
210                    return Ok(result);
211                }
212                _ => {
213                    return Err(Error::BadArgument(format!(
214                        "Unexpected character at position {pos}"
215                    )));
216                }
217            }
218        }
219
220        if !found_any {
221            return Err(Error::BadArgument(
222                "No interval specifiers found".to_string(),
223            ));
224        }
225        Ok(result)
226    }
227}
228
229fn parse_number(bytes: &[u8]) -> Result<(i64, i64, usize)> {
230    let mut number: i64 = 0;
231    let mut fraction: i64 = 0;
232    let mut pos = 0;
233
234    while pos < bytes.len() && bytes[pos].is_ascii_digit() {
235        number = number
236            .checked_mul(10)
237            .ok_or(Error::BadArgument("Number too large".to_string()))?
238            + (bytes[pos] - b'0') as i64;
239        pos += 1;
240    }
241
242    if pos < bytes.len() && bytes[pos] == b'.' {
243        pos += 1;
244        let mut mult: i64 = 100000;
245        while pos < bytes.len() && bytes[pos].is_ascii_digit() {
246            if mult > 0 {
247                fraction += (bytes[pos] - b'0') as i64 * mult;
248            }
249            mult /= 10;
250            pos += 1;
251        }
252    }
253    if pos < bytes.len() && bytes[pos] == b':' {
254        // parse time format HH:MM:SS[.FFFFFF]
255        let time_bytes = &bytes[pos..];
256        let mut time_pos = 0;
257        let mut total_micros: i64 = number * 60 * 60 * MICROS_PER_SEC;
258        let mut colon_count = 0;
259
260        while colon_count < 2 && time_bytes.len() > time_pos {
261            let (minute, _, next_pos) = parse_time_part(&time_bytes[time_pos..])?;
262            let minute_micros = minute * 60 * MICROS_PER_SEC;
263            total_micros += minute_micros;
264            time_pos += next_pos;
265
266            if time_bytes.len() > time_pos && time_bytes[time_pos] == b':' {
267                time_pos += 1;
268                colon_count += 1;
269            } else {
270                break;
271            }
272        }
273        if time_bytes.len() > time_pos {
274            let (seconds, micros, next_pos) = parse_time_part_with_macros(&time_bytes[time_pos..])?;
275            total_micros += seconds * MICROS_PER_SEC + micros;
276            time_pos += next_pos;
277        }
278        return Ok((total_micros, 0, pos + time_pos));
279    }
280
281    if pos == 0 {
282        return Err(Error::BadArgument("Expected number".to_string()));
283    }
284
285    Ok((number, fraction, pos))
286}
287
288fn parse_time_part(bytes: &[u8]) -> Result<(i64, i64, usize)> {
289    let mut number: i64 = 0;
290    let mut pos = 0;
291    while pos < bytes.len() && bytes[pos].is_ascii_digit() {
292        number = number
293            .checked_mul(10)
294            .ok_or(Error::BadArgument("Number too large".to_string()))?
295            + (bytes[pos] - b'0') as i64;
296        pos += 1;
297    }
298    Ok((number, 0, pos))
299}
300
301fn parse_time_part_with_macros(bytes: &[u8]) -> Result<(i64, i64, usize)> {
302    let mut number: i64 = 0;
303    let mut fraction: i64 = 0;
304    let mut pos = 0;
305
306    while pos < bytes.len() && bytes[pos].is_ascii_digit() {
307        number = number
308            .checked_mul(10)
309            .ok_or(Error::BadArgument("Number too large".to_string()))?
310            + (bytes[pos] - b'0') as i64;
311        pos += 1;
312    }
313
314    if pos < bytes.len() && bytes[pos] == b'.' {
315        pos += 1;
316        let mut mult: i64 = 100000;
317        while pos < bytes.len() && bytes[pos].is_ascii_digit() {
318            if mult > 0 {
319                fraction += (bytes[pos] - b'0') as i64 * mult;
320            }
321            mult /= 10;
322            pos += 1;
323        }
324    }
325
326    Ok((number, fraction, pos))
327}
328
329fn parse_identifier(s: &[u8]) -> (String, usize) {
330    let mut pos = 0;
331    while pos < s.len() && (s[pos] == b' ' || s[pos] == b'\t' || s[pos] == b'\n') {
332        pos += 1;
333    }
334    let start_pos = pos;
335    while pos < s.len() && (s[pos].is_ascii_alphabetic()) {
336        pos += 1;
337    }
338
339    if pos == start_pos {
340        return ("".to_string(), pos);
341    }
342
343    let identifier = String::from_utf8_lossy(&s[start_pos..pos]).to_string();
344    (identifier, pos)
345}
346
347#[derive(Debug, PartialEq, Eq)]
348enum DatePartSpecifier {
349    Millennium,
350    Century,
351    Decade,
352    Year,
353    Quarter,
354    Month,
355    Day,
356    Week,
357    Microseconds,
358    Milliseconds,
359    Second,
360    Minute,
361    Hour,
362}
363
364fn try_get_date_part_specifier(specifier_str: &str) -> Result<DatePartSpecifier> {
365    match specifier_str.to_lowercase().as_str() {
366        "millennium" | "millennia" => Ok(DatePartSpecifier::Millennium),
367        "century" | "centuries" => Ok(DatePartSpecifier::Century),
368        "decade" | "decades" => Ok(DatePartSpecifier::Decade),
369        "year" | "years" | "y" => Ok(DatePartSpecifier::Year),
370        "quarter" | "quarters" => Ok(DatePartSpecifier::Quarter),
371        "month" | "months" | "mon" => Ok(DatePartSpecifier::Month),
372        "day" | "days" | "d" => Ok(DatePartSpecifier::Day),
373        "week" | "weeks" | "w" => Ok(DatePartSpecifier::Week),
374        "microsecond" | "microseconds" | "us" => Ok(DatePartSpecifier::Microseconds),
375        "millisecond" | "milliseconds" | "ms" => Ok(DatePartSpecifier::Milliseconds),
376        "second" | "seconds" | "s" => Ok(DatePartSpecifier::Second),
377        "minute" | "minutes" | "m" => Ok(DatePartSpecifier::Minute),
378        "hour" | "hours" | "h" => Ok(DatePartSpecifier::Hour),
379        _ => Err(Error::BadArgument(format!(
380            "Invalid date part specifier: {specifier_str}"
381        ))),
382    }
383}
384
385const MICROS_PER_SEC: i64 = 1_000_000;
386const MICROS_PER_MSEC: i64 = 1_000;
387const MICROS_PER_MINUTE: i64 = 60 * MICROS_PER_SEC;
388const MINROS_PER_HOUR: i64 = 60 * MICROS_PER_MINUTE;
389const DAYS_PER_WEEK: i32 = 7;
390const MONTHS_PER_QUARTER: i32 = 3;
391const MONTHS_PER_YEAR: i32 = 12;
392const MONTHS_PER_DECADE: i32 = 120;
393const MONTHS_PER_CENTURY: i32 = 1200;
394const MONTHS_PER_MILLENNIUM: i32 = 12000;
395
396fn apply_specifier(
397    result: &mut Interval,
398    number: i64,
399    fraction: i64,
400    specifier_str: &str,
401) -> Result<()> {
402    if specifier_str.is_empty() {
403        result.micros = result
404            .micros
405            .checked_add(number)
406            .ok_or(Error::BadArgument("Overflow".to_string()))?;
407        result.micros = result
408            .micros
409            .checked_add(fraction)
410            .ok_or(Error::BadArgument("Overflow".to_string()))?;
411        return Ok(());
412    }
413
414    let specifier = try_get_date_part_specifier(specifier_str)?;
415    match specifier {
416        DatePartSpecifier::Millennium => {
417            result.months = result
418                .months
419                .checked_add(
420                    number
421                        .checked_mul(MONTHS_PER_MILLENNIUM as i64)
422                        .ok_or(Error::BadArgument("Overflow".to_string()))?
423                        .try_into()
424                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
425                )
426                .ok_or(Error::BadArgument("Overflow".to_string()))?;
427        }
428        DatePartSpecifier::Century => {
429            result.months = result
430                .months
431                .checked_add(
432                    number
433                        .checked_mul(MONTHS_PER_CENTURY as i64)
434                        .ok_or(Error::BadArgument("Overflow".to_string()))?
435                        .try_into()
436                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
437                )
438                .ok_or(Error::BadArgument("Overflow".to_string()))?;
439        }
440        DatePartSpecifier::Decade => {
441            result.months = result
442                .months
443                .checked_add(
444                    number
445                        .checked_mul(MONTHS_PER_DECADE as i64)
446                        .ok_or(Error::BadArgument("Overflow".to_string()))?
447                        .try_into()
448                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
449                )
450                .ok_or(Error::BadArgument("Overflow".to_string()))?;
451        }
452        DatePartSpecifier::Year => {
453            result.months = result
454                .months
455                .checked_add(
456                    number
457                        .checked_mul(MONTHS_PER_YEAR as i64)
458                        .ok_or(Error::BadArgument("Overflow".to_string()))?
459                        .try_into()
460                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
461                )
462                .ok_or(Error::BadArgument("Overflow".to_string()))?;
463        }
464        DatePartSpecifier::Quarter => {
465            result.months = result
466                .months
467                .checked_add(
468                    number
469                        .checked_mul(MONTHS_PER_QUARTER as i64)
470                        .ok_or(Error::BadArgument("Overflow".to_string()))?
471                        .try_into()
472                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
473                )
474                .ok_or(Error::BadArgument("Overflow".to_string()))?;
475        }
476        DatePartSpecifier::Month => {
477            result.months = result
478                .months
479                .checked_add(
480                    number
481                        .try_into()
482                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
483                )
484                .ok_or(Error::BadArgument("Overflow".to_string()))?;
485        }
486        DatePartSpecifier::Day => {
487            result.days = result
488                .days
489                .checked_add(
490                    number
491                        .try_into()
492                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
493                )
494                .ok_or(Error::BadArgument("Overflow".to_string()))?;
495        }
496        DatePartSpecifier::Week => {
497            result.days = result
498                .days
499                .checked_add(
500                    number
501                        .checked_mul(DAYS_PER_WEEK as i64)
502                        .ok_or(Error::BadArgument("Overflow".to_string()))?
503                        .try_into()
504                        .map_err(|_| Error::BadArgument("Overflow".to_string()))?,
505                )
506                .ok_or(Error::BadArgument("Overflow".to_string()))?;
507        }
508        DatePartSpecifier::Microseconds => {
509            result.micros = result
510                .micros
511                .checked_add(number)
512                .ok_or(Error::BadArgument("Overflow".to_string()))?;
513        }
514        DatePartSpecifier::Milliseconds => {
515            result.micros = result
516                .micros
517                .checked_add(
518                    number
519                        .checked_mul(MICROS_PER_MSEC)
520                        .ok_or(Error::BadArgument("Overflow".to_string()))?,
521                )
522                .ok_or(Error::BadArgument("Overflow".to_string()))?;
523        }
524        DatePartSpecifier::Second => {
525            result.micros = result
526                .micros
527                .checked_add(
528                    number
529                        .checked_mul(MICROS_PER_SEC)
530                        .ok_or(Error::BadArgument("Overflow".to_string()))?,
531                )
532                .ok_or(Error::BadArgument("Overflow".to_string()))?;
533        }
534        DatePartSpecifier::Minute => {
535            result.micros = result
536                .micros
537                .checked_add(
538                    number
539                        .checked_mul(MICROS_PER_MINUTE)
540                        .ok_or(Error::BadArgument("Overflow".to_string()))?,
541                )
542                .ok_or(Error::BadArgument("Overflow".to_string()))?;
543        }
544        DatePartSpecifier::Hour => {
545            result.micros = result
546                .micros
547                .checked_add(
548                    number
549                        .checked_mul(MINROS_PER_HOUR)
550                        .ok_or(Error::BadArgument("Overflow".to_string()))?,
551                )
552                .ok_or(Error::BadArgument("Overflow".to_string()))?;
553        }
554    }
555    Ok(())
556}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561
562    #[test]
563    fn test_from_string_basic_positive() {
564        let interval = Interval::from_string("0:00:00.000001").unwrap();
565        assert_eq!(interval.micros, 1);
566    }
567}