|
3 | 3 | use crate::scripting::cass_error::{CassError, CassErrorKind};
|
4 | 4 | use crate::scripting::cql_types::Uuid;
|
5 | 5 | use chrono::{NaiveDate, NaiveTime};
|
| 6 | +use regex::Regex; |
6 | 7 | use rune::{Any, ToValue, Value};
|
7 | 8 | use scylla::_macro_internal::ColumnType;
|
8 | 9 | use scylla::frame::response::result::{CollectionType, ColumnSpec, NativeType};
|
9 | 10 | use scylla::response::query_result::ColumnSpecs;
|
10 |
| -use scylla::value::{CqlDate, CqlTime, CqlTimeuuid, CqlValue, CqlVarint}; |
| 11 | +use scylla::value::{CqlDate, CqlDuration, CqlTime, CqlTimeuuid, CqlValue, CqlVarint}; |
11 | 12 | use std::net::IpAddr;
|
12 | 13 | use std::str::FromStr;
|
13 | 14 |
|
14 | 15 | use itertools::*;
|
15 | 16 |
|
16 | 17 | fn to_scylla_value(v: &Value, typ: &ColumnType) -> Result<CqlValue, CassError> {
|
17 |
| - // TODO: add support for the following CQL data types: |
18 |
| - // 'duration' |
19 | 18 | match (v, typ) {
|
20 | 19 | (Value::Bool(v), ColumnType::Native(NativeType::Boolean)) => Ok(CqlValue::Boolean(*v)),
|
21 | 20 |
|
@@ -105,6 +104,74 @@ fn to_scylla_value(v: &Value, typ: &ColumnType) -> Result<CqlValue, CassError> {
|
105 | 104 | let cql_time = CqlTime::try_from(naive_time)?;
|
106 | 105 | Ok(CqlValue::Time(cql_time))
|
107 | 106 | }
|
| 107 | + (Value::String(s), ColumnType::Native(NativeType::Duration)) => { |
| 108 | + // TODO: add support for the following 'ISO 8601' format variants: |
| 109 | + // - ISO 8601 format: P[n]Y[n]M[n]DT[n]H[n]M[n]S or P[n]W |
| 110 | + // - ISO 8601 alternative format: P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss] |
| 111 | + // See: https://opensource.docs.scylladb.com/stable/cql/types.html#working-with-durations |
| 112 | + let duration_str = s.borrow_ref().unwrap(); |
| 113 | + if duration_str.is_empty() { |
| 114 | + return Err(CassError(CassErrorKind::QueryParamConversion( |
| 115 | + format!("{:?}", v), |
| 116 | + "NativeType::Duration".to_string(), |
| 117 | + Some("Duration cannot be empty".to_string()), |
| 118 | + ))); |
| 119 | + } |
| 120 | + // NOTE: we parse the duration explicitly because of the 'CqlDuration' type specifics. |
| 121 | + // It stores only months, days and nanoseconds. |
| 122 | + // So, we do not translate days to months and hours to days because those are ambiguous |
| 123 | + let re = Regex::new(concat!( |
| 124 | + r"(?P<years>\d+)y|", |
| 125 | + r"(?P<months>\d+)mo|", |
| 126 | + r"(?P<weeks>\d+)w|", |
| 127 | + r"(?P<days>\d+)d|", |
| 128 | + r"(?P<hours>\d+)h|", |
| 129 | + r"(?P<seconds>\d+)s|", |
| 130 | + r"(?P<millis>\d+)ms|", |
| 131 | + r"(?P<micros>\d+)us|", |
| 132 | + r"(?P<nanoseconds>\d+)ns|", |
| 133 | + r"(?P<minutes>\d+)m|", // must be after 'mo' and 'ms' matchers |
| 134 | + r"(?P<invalid>.+)", // must be last |
| 135 | + )) |
| 136 | + .unwrap(); |
| 137 | + let (mut months, mut days, mut nanoseconds) = (0, 0, 0); |
| 138 | + for cap in re.captures_iter(&duration_str) { |
| 139 | + if let Some(m) = cap.name("years") { |
| 140 | + months += m.as_str().parse::<i32>().unwrap() * 12; |
| 141 | + } else if let Some(m) = cap.name("months") { |
| 142 | + months += m.as_str().parse::<i32>().unwrap(); |
| 143 | + } else if let Some(m) = cap.name("weeks") { |
| 144 | + days += m.as_str().parse::<i32>().unwrap() * 7; |
| 145 | + } else if let Some(m) = cap.name("days") { |
| 146 | + days += m.as_str().parse::<i32>().unwrap(); |
| 147 | + } else if let Some(m) = cap.name("hours") { |
| 148 | + nanoseconds += m.as_str().parse::<i64>().unwrap() * 3_600_000_000_000; |
| 149 | + } else if let Some(m) = cap.name("minutes") { |
| 150 | + nanoseconds += m.as_str().parse::<i64>().unwrap() * 60_000_000_000; |
| 151 | + } else if let Some(m) = cap.name("seconds") { |
| 152 | + nanoseconds += m.as_str().parse::<i64>().unwrap() * 1_000_000_000; |
| 153 | + } else if let Some(m) = cap.name("millis") { |
| 154 | + nanoseconds += m.as_str().parse::<i64>().unwrap() * 1_000_000; |
| 155 | + } else if let Some(m) = cap.name("micros") { |
| 156 | + nanoseconds += m.as_str().parse::<i64>().unwrap() * 1_000; |
| 157 | + } else if let Some(m) = cap.name("nanoseconds") { |
| 158 | + nanoseconds += m.as_str().parse::<i64>().unwrap(); |
| 159 | + } else if cap.name("invalid").is_some() { |
| 160 | + return Err(CassError(CassErrorKind::QueryParamConversion( |
| 161 | + format!("{:?}", v), |
| 162 | + "NativeType::Duration".to_string(), |
| 163 | + Some("Got invalid duration value".to_string()), |
| 164 | + ))); |
| 165 | + } |
| 166 | + } |
| 167 | + let cql_duration = CqlDuration { |
| 168 | + months, |
| 169 | + days, |
| 170 | + nanoseconds, |
| 171 | + }; |
| 172 | + Ok(CqlValue::Duration(cql_duration)) |
| 173 | + } |
| 174 | + |
108 | 175 | (Value::String(s), ColumnType::Native(NativeType::Varint)) => {
|
109 | 176 | let varint_str = s.borrow_ref().unwrap();
|
110 | 177 | if !varint_str.chars().all(|c| c.is_ascii_digit()) {
|
|
0 commit comments