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    integer: i64,
231    fractional: u64,
232}
233
234impl Decimal {
235    const FRACTIONAL_PART_DENOMINATOR_LOG10: u32 = 19;
236    pub const FRACTIONAL_PART_DENOMINATOR: u64 = 10u64.pow(Decimal::FRACTIONAL_PART_DENOMINATOR_LOG10);
237    pub const MIN: Self = Self::new(i64::MIN, 0);
238    pub const MAX: Self = Self::new(i64::MAX, Decimal::FRACTIONAL_PART_DENOMINATOR - 1);
239
240    pub const fn new(integer: i64, fractional: u64) -> Self {
241        assert!(fractional < Decimal::FRACTIONAL_PART_DENOMINATOR);
242        Self { integer, fractional }
243    }
244
245    /// Get the integer part of the decimal as normal signed 64 bit number
246    pub fn integer_part(&self) -> i64 {
247        self.integer
248    }
249
250    /// Get the fractional part of the decimal, in multiples of 10^-19 (Decimal::FRACTIONAL_PART_DENOMINATOR)
251    /// This means, the smallest decimal representable is 10^-19, and up to 19 decimal places are supported.
252    pub fn fractional_part(&self) -> u64 {
253        self.fractional
254    }
255}
256
257impl Neg for Decimal {
258    type Output = Self;
259
260    fn neg(self) -> Self::Output {
261        Self::default() - self
262    }
263}
264
265impl Add for Decimal {
266    type Output = Self;
267
268    fn add(self, rhs: Self) -> Self::Output {
269        let lhs = self;
270        let (fractional, carry) = match lhs.fractional.overflowing_add(rhs.fractional) {
271            (frac, false) if frac < Self::FRACTIONAL_PART_DENOMINATOR => (frac, 0),
272            (frac, true) if frac < Self::FRACTIONAL_PART_DENOMINATOR => {
273                (frac + 0u64.wrapping_sub(Self::FRACTIONAL_PART_DENOMINATOR), 1)
274            }
275            (frac, false) => (frac - Self::FRACTIONAL_PART_DENOMINATOR, 1),
276            (_, true) => unreachable!(),
277        };
278        let integer = lhs.integer + rhs.integer + carry;
279
280        Self::new(integer, fractional)
281    }
282}
283
284impl Sub for Decimal {
285    type Output = Self;
286
287    fn sub(self, rhs: Self) -> Self::Output {
288        let lhs = self;
289        let (fractional, carry) = match lhs.fractional.overflowing_sub(rhs.fractional) {
290            (frac, false) => (frac, 0),
291            (frac, true) => (frac.wrapping_add(Self::FRACTIONAL_PART_DENOMINATOR), 1),
292        };
293        let integer = lhs.integer - rhs.integer - carry;
294
295        Self::new(integer, fractional)
296    }
297}
298
299impl fmt::Display for Decimal {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        fmt::Debug::fmt(self, f)
302    }
303}
304
305impl fmt::Debug for Decimal {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        if self.fractional == 0 {
308            write!(f, "{}.0", self.integer_part())?;
309        } else {
310            // count number of tailing 0's that don't have to be represented
311            let mut tail_0s = 0;
312            let mut fractional = self.fractional;
313            while fractional.is_multiple_of(10) {
314                tail_0s += 1;
315                fractional /= 10;
316            }
317
318            let fractional_width = Self::FRACTIONAL_PART_DENOMINATOR_LOG10 - tail_0s;
319            write!(f, "{}.{:0width$}dec", self.integer_part(), fractional, width = fractional_width as usize)?;
320        }
321        Ok(())
322    }
323}
324
325/// Offset for datetime-tz. Can be retrieved from an IANA Tz or a FixedOffset.
326#[derive(Copy, Clone, Debug)]
327pub enum Offset {
328    IANA(<Tz as chrono::TimeZone>::Offset),
329    Fixed(FixedOffset),
330}
331
332impl chrono::Offset for Offset {
333    fn fix(&self) -> FixedOffset {
334        match self {
335            Self::IANA(inner) => inner.fix(),
336            Self::Fixed(inner) => inner.fix(),
337        }
338    }
339}
340
341impl fmt::Display for Offset {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        match self {
344            Self::IANA(inner) => fmt::Display::fmt(inner, f),
345            Self::Fixed(inner) => fmt::Display::fmt(inner, f),
346        }
347    }
348}
349
350/// TimeZone for datetime-tz. Can be represented as an IANA Tz or as a FixedOffset.
351#[derive(Copy, Clone, Debug)]
352pub enum TimeZone {
353    IANA(Tz),
354    Fixed(FixedOffset),
355}
356
357impl Default for TimeZone {
358    fn default() -> Self {
359        Self::IANA(Tz::default())
360    }
361}
362
363impl chrono::TimeZone for TimeZone {
364    type Offset = Offset;
365
366    fn from_offset(offset: &Self::Offset) -> Self {
367        match offset {
368            Offset::IANA(offset) => Self::IANA(Tz::from_offset(offset)),
369            Offset::Fixed(offset) => Self::Fixed(FixedOffset::from_offset(offset)),
370        }
371    }
372
373    fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<Self::Offset> {
374        match self {
375            Self::IANA(inner) => inner.offset_from_local_date(local).map(Offset::IANA),
376            Self::Fixed(inner) => inner.offset_from_local_date(local).map(Offset::Fixed),
377        }
378    }
379
380    fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<Self::Offset> {
381        match self {
382            Self::IANA(inner) => inner.offset_from_local_datetime(local).map(Offset::IANA),
383            Self::Fixed(inner) => inner.offset_from_local_datetime(local).map(Offset::Fixed),
384        }
385    }
386
387    fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset {
388        match self {
389            TimeZone::IANA(inner) => Offset::IANA(inner.offset_from_utc_date(utc)),
390            TimeZone::Fixed(inner) => Offset::Fixed(inner.offset_from_utc_date(utc)),
391        }
392    }
393
394    fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
395        match self {
396            TimeZone::IANA(inner) => Offset::IANA(inner.offset_from_utc_datetime(utc)),
397            TimeZone::Fixed(inner) => Offset::Fixed(inner.offset_from_utc_datetime(utc)),
398        }
399    }
400}
401
402/// A relative duration, which contains months, days, and nanoseconds.
403/// Can be used for calendar-relative durations (eg 7 days forward), or for absolute durations using the nanosecond component
404/// When used as an absolute duration, convertible to chrono::Duration
405#[repr(C)]
406#[derive(Clone, Copy, Hash, PartialEq, Eq)]
407pub struct Duration {
408    pub months: u32,
409    pub days: u32,
410    pub nanos: u64,
411}
412
413impl Duration {
414    pub const NANOS_PER_SEC: u64 = 1_000_000_000;
415    #[doc(hidden)]
416    /// cbindgen:ignore
417    pub const NANOS_PER_MINUTE: u64 = 60 * Self::NANOS_PER_SEC;
418    #[doc(hidden)]
419    /// cbindgen:ignore
420    pub const NANOS_PER_HOUR: u64 = 60 * 60 * Self::NANOS_PER_SEC;
421    pub const DAYS_PER_WEEK: u32 = 7;
422    pub const MONTHS_PER_YEAR: u32 = 12;
423
424    pub fn new(months: u32, days: u32, nanos: u64) -> Self {
425        Self { months, days, nanos }
426    }
427
428    pub fn months(&self) -> u32 {
429        self.months
430    }
431
432    pub fn days(&self) -> u32 {
433        self.days
434    }
435
436    pub fn nanos(&self) -> u64 {
437        self.nanos
438    }
439
440    fn is_empty(&self) -> bool {
441        self.months == 0 && self.days == 0 && self.nanos == 0
442    }
443}
444
445impl TryFrom<Duration> for chrono::Duration {
446    type Error = crate::Error;
447
448    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
449        if duration.months != 0 || duration.days != 0 {
450            Err(Error::Other(String::from(
451                "Converting TypeDB duration to chrono::Duration is only possible when months and days are not set.",
452            )))
453        } else {
454            match i64::try_from(duration.nanos) {
455                Ok(nanos) => Ok(chrono::Duration::nanoseconds(nanos)),
456                Err(_) => {
457                    Err(Error::Other(String::from("Duration u64 nanos exceeded i64 required for chrono::Duration")))
458                }
459            }
460        }
461    }
462}
463
464// TODO: Duration parsing is basically a copy of TypeDB server's code, can be made a dependency.
465#[derive(Debug)]
466pub struct DurationParseError;
467
468struct Segment {
469    number: u64,
470    symbol: u8,
471    number_len: usize,
472}
473
474fn read_u32(str: &str) -> Result<(Segment, &str), DurationParseError> {
475    let mut i = 0;
476    while i + 1 < str.len() && str.as_bytes()[i].is_ascii_digit() {
477        i += 1;
478    }
479    if i == 0 {
480        return Err(DurationParseError);
481    }
482    let value = str[..i].parse().map_err(|_| DurationParseError)?;
483    Ok((Segment { number: value, symbol: str.as_bytes()[i], number_len: i }, &str[i + 1..]))
484}
485
486impl FromStr for Duration {
487    type Err = DurationParseError;
488
489    fn from_str(mut str: &str) -> Result<Self, Self::Err> {
490        let mut months = 0;
491        let mut days = 0;
492        let mut nanos = 0;
493
494        if str.as_bytes()[0] != b'P' {
495            return Err(DurationParseError);
496        }
497        str = &str[1..];
498
499        let mut parsing_time = false;
500        let mut previous_symbol = None;
501        while !str.is_empty() {
502            if str.as_bytes()[0] == b'T' {
503                parsing_time = true;
504                str = &str[1..];
505            }
506
507            let (Segment { number, symbol, number_len }, tail) = read_u32(str)?;
508            str = tail;
509
510            if previous_symbol == Some(b'.') && symbol != b'S' {
511                return Err(DurationParseError);
512            }
513
514            match symbol {
515                b'Y' => months += number as u32 * Self::MONTHS_PER_YEAR,
516                b'M' if !parsing_time => months += number as u32,
517
518                b'W' => days += number as u32 * Self::DAYS_PER_WEEK,
519                b'D' => days += number as u32,
520
521                b'H' => nanos += number * Self::NANOS_PER_HOUR,
522                b'M' if parsing_time => nanos += number * Self::NANOS_PER_MINUTE,
523                b'.' => nanos += number * Self::NANOS_PER_SEC,
524                b'S' if previous_symbol != Some(b'.') => nanos += number * Self::NANOS_PER_SEC,
525                b'S' if previous_symbol == Some(b'.') => nanos += number * 10u64.pow(9 - number_len as u32),
526                _ => return Err(DurationParseError),
527            }
528            previous_symbol = Some(symbol);
529        }
530
531        Ok(Self { months, days, nanos })
532    }
533}
534
535/// ISO-8601 compliant representation of a duration
536impl fmt::Display for Duration {
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        if self.is_empty() {
539            f.write_str("PT0S")?;
540            return Ok(());
541        }
542
543        write!(f, "P")?;
544
545        if self.months > 0 || self.days > 0 {
546            let years = self.months / Self::MONTHS_PER_YEAR;
547            let months = self.months % Self::MONTHS_PER_YEAR;
548            let days = self.days;
549            if years > 0 {
550                write!(f, "{years}Y")?;
551            }
552            if months > 0 {
553                write!(f, "{months}M")?;
554            }
555            if days > 0 {
556                write!(f, "{days}D")?;
557            }
558        }
559
560        if self.nanos > 0 {
561            write!(f, "T")?;
562
563            let hours = self.nanos / Self::NANOS_PER_HOUR;
564            let minutes = (self.nanos % Self::NANOS_PER_HOUR) / Self::NANOS_PER_MINUTE;
565            let seconds = (self.nanos % Self::NANOS_PER_MINUTE) / Self::NANOS_PER_SEC;
566            let nanos = self.nanos % Self::NANOS_PER_SEC;
567
568            if hours > 0 {
569                write!(f, "{hours}H")?;
570            }
571            if minutes > 0 {
572                write!(f, "{minutes}M")?;
573            }
574            if seconds > 0 && nanos == 0 {
575                write!(f, "{seconds}S")?;
576            } else if nanos > 0 {
577                write!(f, "{seconds}.{nanos:09}S")?;
578            }
579        }
580
581        Ok(())
582    }
583}
584
585impl fmt::Debug for Duration {
586    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587        write!(f, "months: {}, days: {}, nanos: {}", self.months, self.days, self.nanos)
588    }
589}
590
591#[derive(Clone, PartialEq)]
592pub struct Struct {
593    pub(crate) fields: HashMap<String, Option<Value>>,
594}
595
596impl Struct {
597    pub fn fields(&self) -> &HashMap<String, Option<Value>> {
598        &self.fields
599    }
600}
601
602impl fmt::Display for Struct {
603    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604        fmt::Debug::fmt(self, f)
605    }
606}
607
608impl fmt::Debug for Struct {
609    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610        write!(f, "{:?}", self.fields)
611    }
612}