Skip to main content

typedb_driver/concept/
value.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20use std::{
21    collections::HashMap,
22    fmt,
23    ops::{Add, Neg, Sub},
24    str::FromStr,
25};
26
27use chrono::{DateTime, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime};
28use chrono_tz::Tz;
29
30use crate::Error;
31
32/// Represents the type of primitive value is held by a Value or Attribute.
33#[derive(Clone, PartialEq, Eq)]
34pub enum ValueType {
35    Boolean,
36    Integer,
37    Double,
38    Decimal,
39    String,
40    Date,
41    Datetime,
42    DatetimeTZ,
43    Duration,
44    Struct(String),
45}
46
47impl ValueType {
48    pub(crate) const NONE_STR: &'static str = "none";
49    pub(crate) const BOOLEAN_STR: &'static str = "boolean";
50    pub(crate) const INTEGER_STR: &'static str = "integer";
51    pub(crate) const DOUBLE_STR: &'static str = "double";
52    pub(crate) const DECIMAL_STR: &'static str = "decimal";
53    pub(crate) const STRING_STR: &'static str = "string";
54    pub(crate) const DATE_STR: &'static str = "date";
55    pub(crate) const DATETIME_STR: &'static str = "datetime";
56    pub(crate) const DATETIME_TZ_STR: &'static str = "datetime-tz";
57    pub(crate) const DURATION_STR: &'static str = "duration";
58
59    pub fn name(&self) -> &str {
60        match self {
61            Self::Boolean => Self::BOOLEAN_STR,
62            Self::Integer => Self::INTEGER_STR,
63            Self::Double => Self::DOUBLE_STR,
64            Self::Decimal => Self::DECIMAL_STR,
65            Self::String => Self::STRING_STR,
66            Self::Date => Self::DATE_STR,
67            Self::Datetime => Self::DATETIME_STR,
68            Self::DatetimeTZ => Self::DATETIME_TZ_STR,
69            Self::Duration => Self::DURATION_STR,
70            Self::Struct(name) => name,
71        }
72    }
73}
74
75impl fmt::Display for ValueType {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        fmt::Debug::fmt(self, f)
78    }
79}
80
81impl fmt::Debug for ValueType {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}", self.name())
84    }
85}
86
87#[derive(Clone, PartialEq)]
88pub enum Value {
89    Boolean(bool),
90    Integer(i64),
91    Double(f64),
92    Decimal(Decimal),
93    String(String),
94    Date(NaiveDate),
95    Datetime(NaiveDateTime),
96    DatetimeTZ(DateTime<TimeZone>),
97    Duration(Duration),
98    Struct(Struct, String),
99}
100
101impl Value {
102    /// Retrieves the `ValueType` of this value concept.
103    ///
104    /// # Examples
105    ///
106    /// ```rust
107    /// value.get_type();
108    /// ```
109    pub fn get_type(&self) -> ValueType {
110        match self {
111            Self::Boolean(_) => ValueType::Boolean,
112            Self::Integer(_) => ValueType::Integer,
113            Self::Double(_) => ValueType::Double,
114            Self::String(_) => ValueType::String,
115            Self::Decimal(_) => ValueType::Decimal,
116            Self::Date(_) => ValueType::Date,
117            Self::Datetime(_) => ValueType::Datetime,
118            Self::DatetimeTZ(_) => ValueType::DatetimeTZ,
119            Self::Duration(_) => ValueType::Duration,
120            Self::Struct(_, struct_type_name) => ValueType::Struct(struct_type_name.clone()),
121        }
122    }
123
124    /// Retrieves the name of the `ValueType` of this value concept.
125    ///
126    /// # Examples
127    ///
128    /// ```rust
129    /// value.get_type_name();
130    /// ```
131    pub fn get_type_name(&self) -> &str {
132        match self {
133            Self::Boolean(_) => ValueType::Boolean.name(),
134            Self::Integer(_) => ValueType::Integer.name(),
135            Self::Double(_) => ValueType::Double.name(),
136            Self::String(_) => ValueType::String.name(),
137            Self::Decimal(_) => ValueType::Decimal.name(),
138            Self::Date(_) => ValueType::Date.name(),
139            Self::Datetime(_) => ValueType::Datetime.name(),
140            Self::DatetimeTZ(_) => ValueType::DatetimeTZ.name(),
141            Self::Duration(_) => ValueType::Duration.name(),
142            Self::Struct(_, struct_type_name) => struct_type_name,
143        }
144    }
145
146    pub fn get_boolean(&self) -> Option<bool> {
147        if let Value::Boolean(bool) = self { Some(*bool) } else { None }
148    }
149
150    pub fn get_integer(&self) -> Option<i64> {
151        if let Value::Integer(integer) = self { Some(*integer) } else { None }
152    }
153
154    pub fn get_double(&self) -> Option<f64> {
155        if let Value::Double(double) = self { Some(*double) } else { None }
156    }
157
158    pub fn get_string(&self) -> Option<&str> {
159        if let Value::String(string) = self { Some(&**string) } else { None }
160    }
161
162    pub fn get_decimal(&self) -> Option<Decimal> {
163        if let Value::Decimal(decimal) = self { Some(*decimal) } else { None }
164    }
165
166    pub fn get_date(&self) -> Option<NaiveDate> {
167        if let Value::Date(naive_date) = self { Some(*naive_date) } else { None }
168    }
169
170    pub fn get_datetime(&self) -> Option<NaiveDateTime> {
171        if let Value::Datetime(datetime) = self { Some(*datetime) } else { None }
172    }
173
174    pub fn get_datetime_tz(&self) -> Option<DateTime<TimeZone>> {
175        if let Value::DatetimeTZ(datetime_tz) = self { Some(*datetime_tz) } else { None }
176    }
177
178    pub fn get_duration(&self) -> Option<Duration> {
179        if let Value::Duration(duration) = self { Some(*duration) } else { None }
180    }
181
182    pub fn get_struct(&self) -> Option<&Struct> {
183        if let Value::Struct(struct_, _) = self { Some(struct_) } else { None }
184    }
185}
186
187impl fmt::Display for Value {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        match self {
190            Self::Boolean(bool) => write!(f, "{}", bool),
191            Self::Integer(integer) => write!(f, "{}", integer),
192            Self::Double(double) => write!(f, "{}", double),
193            Self::String(string) => write!(f, "\"{}\"", string),
194            Self::Decimal(decimal) => write!(f, "{}", decimal),
195            Self::Date(date) => write!(f, "{}", date.format("%Y-%m-%d")),
196            Self::Datetime(datetime) => write!(f, "{}", datetime.format("%FT%T%.9f")),
197            Self::DatetimeTZ(datetime_tz) => match datetime_tz.timezone() {
198                TimeZone::IANA(tz) => write!(f, "{} {}", datetime_tz.format("%FT%T%.9f"), tz.name()),
199                TimeZone::Fixed(_) => write!(f, "{}", datetime_tz.format("%FT%T%.9f%:z")),
200            },
201            Self::Duration(duration) => write!(f, "{}", duration),
202            Self::Struct(value, name) => write!(f, "{} {}", name, value),
203        }
204    }
205}
206
207impl fmt::Debug for Value {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        write!(f, "{}: ", self.get_type_name())?;
210        match self {
211            Value::Boolean(bool) => write!(f, "{}", bool),
212            Value::Integer(integer) => write!(f, "{}", integer),
213            Value::Double(double) => write!(f, "{}", double),
214            Value::Decimal(decimal) => write!(f, "{}", decimal),
215            Value::String(string) => write!(f, "\"{}\"", string),
216            Value::Date(date) => write!(f, "{}", date),
217            Value::Datetime(datetime) => write!(f, "{}", datetime),
218            Value::DatetimeTZ(datetime_tz) => write!(f, "{}", datetime_tz),
219            Value::Duration(duration) => write!(f, "{}", duration),
220            Value::Struct(value, name) => write!(f, "{} {}", name, value),
221        }
222    }
223}
224
225/// A fixed-point decimal number.
226/// Holds exactly 19 digits after the decimal point and a 64-bit value before the decimal point.
227#[repr(C)]
228#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
229pub struct Decimal {
230    /// The integer part of the decimal as normal signed 64 bit number
231    pub integer: i64,
232    /// The fractional part of the decimal, in multiples of 10^-19 (Decimal::FRACTIONAL_PART_DENOMINATOR).
233    /// This means that the smallest decimal representable is 10^-19, and up to 19 decimal places are supported.
234    pub fractional: u64,
235}
236
237impl Decimal {
238    const FRACTIONAL_PART_DENOMINATOR_LOG10: u32 = 19;
239    pub const FRACTIONAL_PART_DENOMINATOR: u64 = 10u64.pow(Decimal::FRACTIONAL_PART_DENOMINATOR_LOG10);
240    pub const MIN: Self = Self::new(i64::MIN, 0);
241    pub const MAX: Self = Self::new(i64::MAX, Decimal::FRACTIONAL_PART_DENOMINATOR - 1);
242
243    pub const fn new(integer: i64, fractional: u64) -> Self {
244        assert!(fractional < Decimal::FRACTIONAL_PART_DENOMINATOR);
245        Self { integer, fractional }
246    }
247}
248
249impl Neg for Decimal {
250    type Output = Self;
251
252    fn neg(self) -> Self::Output {
253        Self::default() - self
254    }
255}
256
257impl Add for Decimal {
258    type Output = Self;
259
260    fn add(self, rhs: Self) -> Self::Output {
261        let lhs = self;
262        let (fractional, carry) = match lhs.fractional.overflowing_add(rhs.fractional) {
263            (frac, false) if frac < Self::FRACTIONAL_PART_DENOMINATOR => (frac, 0),
264            (frac, true) if frac < Self::FRACTIONAL_PART_DENOMINATOR => {
265                (frac + 0u64.wrapping_sub(Self::FRACTIONAL_PART_DENOMINATOR), 1)
266            }
267            (frac, false) => (frac - Self::FRACTIONAL_PART_DENOMINATOR, 1),
268            (_, true) => unreachable!(),
269        };
270        let integer = lhs.integer + rhs.integer + carry;
271
272        Self::new(integer, fractional)
273    }
274}
275
276impl Sub for Decimal {
277    type Output = Self;
278
279    fn sub(self, rhs: Self) -> Self::Output {
280        let lhs = self;
281        let (fractional, carry) = match lhs.fractional.overflowing_sub(rhs.fractional) {
282            (frac, false) => (frac, 0),
283            (frac, true) => (frac.wrapping_add(Self::FRACTIONAL_PART_DENOMINATOR), 1),
284        };
285        let integer = lhs.integer - rhs.integer - carry;
286
287        Self::new(integer, fractional)
288    }
289}
290
291impl fmt::Display for Decimal {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        fmt::Debug::fmt(self, f)
294    }
295}
296
297impl fmt::Debug for Decimal {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        if self.fractional == 0 {
300            write!(f, "{}.0", self.integer)?;
301        } else {
302            // count number of tailing 0's that don't have to be represented
303            let mut tail_0s = 0;
304            let mut fractional = self.fractional;
305            while fractional.is_multiple_of(10) {
306                tail_0s += 1;
307                fractional /= 10;
308            }
309
310            let fractional_width = Self::FRACTIONAL_PART_DENOMINATOR_LOG10 - tail_0s;
311            write!(f, "{}.{:0width$}dec", self.integer, fractional, width = fractional_width as usize)?;
312        }
313        Ok(())
314    }
315}
316
317/// Offset for datetime-tz. Can be retrieved from an IANA Tz or a FixedOffset.
318#[derive(Copy, Clone, Debug)]
319pub enum Offset {
320    IANA(<Tz as chrono::TimeZone>::Offset),
321    Fixed(FixedOffset),
322}
323
324impl chrono::Offset for Offset {
325    fn fix(&self) -> FixedOffset {
326        match self {
327            Self::IANA(inner) => inner.fix(),
328            Self::Fixed(inner) => inner.fix(),
329        }
330    }
331}
332
333impl fmt::Display for Offset {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        match self {
336            Self::IANA(inner) => fmt::Display::fmt(inner, f),
337            Self::Fixed(inner) => fmt::Display::fmt(inner, f),
338        }
339    }
340}
341
342/// TimeZone for datetime-tz. Can be represented as an IANA Tz or as a FixedOffset.
343#[derive(Copy, Clone, Debug)]
344pub enum TimeZone {
345    IANA(Tz),
346    Fixed(FixedOffset),
347}
348
349impl Default for TimeZone {
350    fn default() -> Self {
351        Self::IANA(Tz::default())
352    }
353}
354
355impl chrono::TimeZone for TimeZone {
356    type Offset = Offset;
357
358    fn from_offset(offset: &Self::Offset) -> Self {
359        match offset {
360            Offset::IANA(offset) => Self::IANA(Tz::from_offset(offset)),
361            Offset::Fixed(offset) => Self::Fixed(FixedOffset::from_offset(offset)),
362        }
363    }
364
365    fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<Self::Offset> {
366        match self {
367            Self::IANA(inner) => inner.offset_from_local_date(local).map(Offset::IANA),
368            Self::Fixed(inner) => inner.offset_from_local_date(local).map(Offset::Fixed),
369        }
370    }
371
372    fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<Self::Offset> {
373        match self {
374            Self::IANA(inner) => inner.offset_from_local_datetime(local).map(Offset::IANA),
375            Self::Fixed(inner) => inner.offset_from_local_datetime(local).map(Offset::Fixed),
376        }
377    }
378
379    fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset {
380        match self {
381            TimeZone::IANA(inner) => Offset::IANA(inner.offset_from_utc_date(utc)),
382            TimeZone::Fixed(inner) => Offset::Fixed(inner.offset_from_utc_date(utc)),
383        }
384    }
385
386    fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
387        match self {
388            TimeZone::IANA(inner) => Offset::IANA(inner.offset_from_utc_datetime(utc)),
389            TimeZone::Fixed(inner) => Offset::Fixed(inner.offset_from_utc_datetime(utc)),
390        }
391    }
392}
393
394/// A relative duration, which contains months, days, and nanoseconds.
395/// Can be used for calendar-relative durations (eg 7 days forward), or for absolute durations using the nanosecond component
396/// When used as an absolute duration, convertible to chrono::Duration
397#[repr(C)]
398#[derive(Clone, Copy, Hash, PartialEq, Eq)]
399pub struct Duration {
400    /// Number of calendar months in the duration.
401    pub months: u32,
402    /// Number of calendar days in the duration.
403    pub days: u32,
404    /// Number of nanoseconds in the duration.
405    pub nanos: u64,
406}
407
408impl Duration {
409    /// Number of nanoseconds in one second.
410    pub const NANOS_PER_SEC: u64 = 1_000_000_000;
411    #[doc(hidden)]
412    /// cbindgen:ignore
413    pub const NANOS_PER_MINUTE: u64 = 60 * Self::NANOS_PER_SEC;
414    #[doc(hidden)]
415    /// cbindgen:ignore
416    pub const NANOS_PER_HOUR: u64 = 60 * 60 * Self::NANOS_PER_SEC;
417    /// Number of days in one week.
418    pub const DAYS_PER_WEEK: u32 = 7;
419    /// Number of months in one year.
420    pub const MONTHS_PER_YEAR: u32 = 12;
421
422    pub fn new(months: u32, days: u32, nanos: u64) -> Self {
423        Self { months, days, nanos }
424    }
425
426    fn is_empty(&self) -> bool {
427        self.months == 0 && self.days == 0 && self.nanos == 0
428    }
429}
430
431impl TryFrom<Duration> for chrono::Duration {
432    type Error = crate::Error;
433
434    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
435        if duration.months != 0 || duration.days != 0 {
436            Err(Error::Other(String::from(
437                "Converting TypeDB duration to chrono::Duration is only possible when months and days are not set.",
438            )))
439        } else {
440            match i64::try_from(duration.nanos) {
441                Ok(nanos) => Ok(chrono::Duration::nanoseconds(nanos)),
442                Err(_) => {
443                    Err(Error::Other(String::from("Duration u64 nanos exceeded i64 required for chrono::Duration")))
444                }
445            }
446        }
447    }
448}
449
450// TODO: Duration parsing is basically a copy of TypeDB server's code, can be made a dependency.
451#[derive(Debug)]
452pub struct DurationParseError;
453
454struct Segment {
455    number: u64,
456    symbol: u8,
457    number_len: usize,
458}
459
460fn read_u32(str: &str) -> Result<(Segment, &str), DurationParseError> {
461    let mut i = 0;
462    while i + 1 < str.len() && str.as_bytes()[i].is_ascii_digit() {
463        i += 1;
464    }
465    if i == 0 {
466        return Err(DurationParseError);
467    }
468    let value = str[..i].parse().map_err(|_| DurationParseError)?;
469    Ok((Segment { number: value, symbol: str.as_bytes()[i], number_len: i }, &str[i + 1..]))
470}
471
472impl FromStr for Duration {
473    type Err = DurationParseError;
474
475    fn from_str(mut str: &str) -> Result<Self, Self::Err> {
476        let mut months = 0;
477        let mut days = 0;
478        let mut nanos = 0;
479
480        if str.as_bytes()[0] != b'P' {
481            return Err(DurationParseError);
482        }
483        str = &str[1..];
484
485        let mut parsing_time = false;
486        let mut previous_symbol = None;
487        while !str.is_empty() {
488            if str.as_bytes()[0] == b'T' {
489                parsing_time = true;
490                str = &str[1..];
491            }
492
493            let (Segment { number, symbol, number_len }, tail) = read_u32(str)?;
494            str = tail;
495
496            if previous_symbol == Some(b'.') && symbol != b'S' {
497                return Err(DurationParseError);
498            }
499
500            match symbol {
501                b'Y' => months += number as u32 * Self::MONTHS_PER_YEAR,
502                b'M' if !parsing_time => months += number as u32,
503
504                b'W' => days += number as u32 * Self::DAYS_PER_WEEK,
505                b'D' => days += number as u32,
506
507                b'H' => nanos += number * Self::NANOS_PER_HOUR,
508                b'M' if parsing_time => nanos += number * Self::NANOS_PER_MINUTE,
509                b'.' => nanos += number * Self::NANOS_PER_SEC,
510                b'S' if previous_symbol != Some(b'.') => nanos += number * Self::NANOS_PER_SEC,
511                b'S' if previous_symbol == Some(b'.') => nanos += number * 10u64.pow(9 - number_len as u32),
512                _ => return Err(DurationParseError),
513            }
514            previous_symbol = Some(symbol);
515        }
516
517        Ok(Self { months, days, nanos })
518    }
519}
520
521/// ISO-8601 compliant representation of a duration
522impl fmt::Display for Duration {
523    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
524        if self.is_empty() {
525            f.write_str("PT0S")?;
526            return Ok(());
527        }
528
529        write!(f, "P")?;
530
531        if self.months > 0 || self.days > 0 {
532            let years = self.months / Self::MONTHS_PER_YEAR;
533            let months = self.months % Self::MONTHS_PER_YEAR;
534            let days = self.days;
535            if years > 0 {
536                write!(f, "{years}Y")?;
537            }
538            if months > 0 {
539                write!(f, "{months}M")?;
540            }
541            if days > 0 {
542                write!(f, "{days}D")?;
543            }
544        }
545
546        if self.nanos > 0 {
547            write!(f, "T")?;
548
549            let hours = self.nanos / Self::NANOS_PER_HOUR;
550            let minutes = (self.nanos % Self::NANOS_PER_HOUR) / Self::NANOS_PER_MINUTE;
551            let seconds = (self.nanos % Self::NANOS_PER_MINUTE) / Self::NANOS_PER_SEC;
552            let nanos = self.nanos % Self::NANOS_PER_SEC;
553
554            if hours > 0 {
555                write!(f, "{hours}H")?;
556            }
557            if minutes > 0 {
558                write!(f, "{minutes}M")?;
559            }
560            if seconds > 0 && nanos == 0 {
561                write!(f, "{seconds}S")?;
562            } else if nanos > 0 {
563                write!(f, "{seconds}.{nanos:09}S")?;
564            }
565        }
566
567        Ok(())
568    }
569}
570
571impl fmt::Debug for Duration {
572    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
573        write!(f, "months: {}, days: {}, nanos: {}", self.months, self.days, self.nanos)
574    }
575}
576
577#[derive(Clone, PartialEq)]
578pub struct Struct {
579    pub(crate) fields: HashMap<String, Option<Value>>,
580}
581
582impl Struct {
583    pub fn fields(&self) -> &HashMap<String, Option<Value>> {
584        &self.fields
585    }
586}
587
588impl fmt::Display for Struct {
589    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590        fmt::Debug::fmt(self, f)
591    }
592}
593
594impl fmt::Debug for Struct {
595    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596        write!(f, "{:?}", self.fields)
597    }
598}