Skip to content

Commit 4b26327

Browse files
committed
Add support for the Duration CQL data type
Example of the Duration CQL data type usage in a rune script: let d1 = "1y3mo2w6d13h14m22s33ms44us55ns"; let d2 = "55mo345d11ns"; let d3 = "1m5s3ms"; let d4 = "5s";
1 parent 48856ef commit 4b26327

File tree

1 file changed

+70
-3
lines changed

1 file changed

+70
-3
lines changed

src/scripting/bind.rs

+70-3
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@
33
use crate::scripting::cass_error::{CassError, CassErrorKind};
44
use crate::scripting::cql_types::Uuid;
55
use chrono::{NaiveDate, NaiveTime};
6+
use regex::Regex;
67
use rune::{Any, ToValue, Value};
78
use scylla::_macro_internal::ColumnType;
89
use scylla::frame::response::result::{CollectionType, ColumnSpec, NativeType};
910
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};
1112
use std::net::IpAddr;
1213
use std::str::FromStr;
1314

1415
use itertools::*;
1516

1617
fn to_scylla_value(v: &Value, typ: &ColumnType) -> Result<CqlValue, CassError> {
17-
// TODO: add support for the following CQL data types:
18-
// 'duration'
1918
match (v, typ) {
2019
(Value::Bool(v), ColumnType::Native(NativeType::Boolean)) => Ok(CqlValue::Boolean(*v)),
2120

@@ -105,6 +104,74 @@ fn to_scylla_value(v: &Value, typ: &ColumnType) -> Result<CqlValue, CassError> {
105104
let cql_time = CqlTime::try_from(naive_time)?;
106105
Ok(CqlValue::Time(cql_time))
107106
}
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+
108175
(Value::String(s), ColumnType::Native(NativeType::Varint)) => {
109176
let varint_str = s.borrow_ref().unwrap();
110177
if !varint_str.chars().all(|c| c.is_ascii_digit()) {

0 commit comments

Comments
 (0)