databend_driver_core/value/
interval.rs1use 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 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}