1use scylla::frame::response::result::{CqlValue, Row};
4use serde::de::DeserializeOwned;
5
6use crate::error::{ScyllaError, ScyllaResult};
7use crate::types::ScyllaValue;
8
9pub trait FromScyllaRow: Sized {
11 fn from_row(row: &Row) -> ScyllaResult<Self>;
13}
14
15pub struct RowAccessor<'a> {
17 row: &'a Row,
18}
19
20impl<'a> RowAccessor<'a> {
21 #[must_use]
23 pub fn new(row: &'a Row) -> Self {
24 Self { row }
25 }
26
27 pub fn get<T: FromCqlValue>(&self, index: usize) -> ScyllaResult<T> {
29 self.row
30 .columns
31 .get(index)
32 .ok_or_else(|| {
33 ScyllaError::deserialization(format!("Column index {index} out of bounds"))
34 })?
35 .as_ref()
36 .map(|v| T::from_cql(v))
37 .transpose()?
38 .ok_or_else(|| ScyllaError::deserialization(format!("Column {index} is null")))
39 }
40
41 pub fn get_opt<T: FromCqlValue>(&self, index: usize) -> ScyllaResult<Option<T>> {
43 match self.row.columns.get(index) {
44 Some(Some(value)) => Ok(Some(T::from_cql(value)?)),
45 Some(None) | None => Ok(None),
46 }
47 }
48
49 #[must_use]
51 pub fn len(&self) -> usize {
52 self.row.columns.len()
53 }
54
55 #[must_use]
57 pub fn is_empty(&self) -> bool {
58 self.row.columns.is_empty()
59 }
60}
61
62pub trait FromCqlValue: Sized {
64 fn from_cql(value: &CqlValue) -> ScyllaResult<Self>;
66}
67
68impl FromCqlValue for bool {
69 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
70 match value {
71 CqlValue::Boolean(v) => Ok(*v),
72 _ => Err(ScyllaError::type_conversion("Expected boolean")),
73 }
74 }
75}
76
77impl FromCqlValue for i8 {
78 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
79 match value {
80 CqlValue::TinyInt(v) => Ok(*v),
81 _ => Err(ScyllaError::type_conversion("Expected tinyint")),
82 }
83 }
84}
85
86impl FromCqlValue for i16 {
87 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
88 match value {
89 CqlValue::SmallInt(v) => Ok(*v),
90 _ => Err(ScyllaError::type_conversion("Expected smallint")),
91 }
92 }
93}
94
95impl FromCqlValue for i32 {
96 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
97 match value {
98 CqlValue::Int(v) => Ok(*v),
99 _ => Err(ScyllaError::type_conversion("Expected int")),
100 }
101 }
102}
103
104impl FromCqlValue for i64 {
105 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
106 match value {
107 CqlValue::BigInt(v) => Ok(*v),
108 CqlValue::Counter(v) => Ok(v.0),
109 _ => Err(ScyllaError::type_conversion("Expected bigint")),
110 }
111 }
112}
113
114impl FromCqlValue for f32 {
115 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
116 match value {
117 CqlValue::Float(v) => Ok(*v),
118 _ => Err(ScyllaError::type_conversion("Expected float")),
119 }
120 }
121}
122
123impl FromCqlValue for f64 {
124 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
125 match value {
126 CqlValue::Double(v) => Ok(*v),
127 CqlValue::Float(v) => Ok(f64::from(*v)),
128 _ => Err(ScyllaError::type_conversion("Expected double")),
129 }
130 }
131}
132
133impl FromCqlValue for String {
134 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
135 match value {
136 CqlValue::Text(v) | CqlValue::Ascii(v) => Ok(v.clone()),
137 _ => Err(ScyllaError::type_conversion("Expected text")),
138 }
139 }
140}
141
142impl FromCqlValue for Vec<u8> {
143 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
144 match value {
145 CqlValue::Blob(v) => Ok(v.clone()),
146 _ => Err(ScyllaError::type_conversion("Expected blob")),
147 }
148 }
149}
150
151impl FromCqlValue for uuid::Uuid {
152 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
153 match value {
154 CqlValue::Uuid(v) => Ok(*v),
155 CqlValue::Timeuuid(v) => Ok((*v).into()),
156 _ => Err(ScyllaError::type_conversion("Expected uuid")),
157 }
158 }
159}
160
161impl FromCqlValue for chrono::DateTime<chrono::Utc> {
162 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
163 match value {
164 CqlValue::Timestamp(ts) => chrono::DateTime::from_timestamp_millis(ts.0)
165 .ok_or_else(|| ScyllaError::type_conversion("Invalid timestamp")),
166 _ => Err(ScyllaError::type_conversion("Expected timestamp")),
167 }
168 }
169}
170
171impl FromCqlValue for chrono::NaiveDate {
172 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
173 match value {
174 CqlValue::Date(d) => {
177 let days_from_ce = i64::from(d.0) - (1i64 << 31) + 719_163;
178 i32::try_from(days_from_ce)
179 .ok()
180 .and_then(chrono::NaiveDate::from_num_days_from_ce_opt)
181 .ok_or_else(|| ScyllaError::type_conversion("Invalid date"))
182 }
183 _ => Err(ScyllaError::type_conversion("Expected date")),
184 }
185 }
186}
187
188impl FromCqlValue for std::net::IpAddr {
189 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
190 match value {
191 CqlValue::Inet(v) => Ok(*v),
192 _ => Err(ScyllaError::type_conversion("Expected inet")),
193 }
194 }
195}
196
197impl FromCqlValue for ScyllaValue {
198 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
199 Ok(value.clone().into())
200 }
201}
202
203impl<T: FromCqlValue> FromCqlValue for Option<T> {
204 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
205 match value {
206 CqlValue::Empty => Ok(None),
207 _ => Ok(Some(T::from_cql(value)?)),
208 }
209 }
210}
211
212impl<T: FromCqlValue> FromCqlValue for Vec<T> {
213 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
214 match value {
215 CqlValue::List(items) | CqlValue::Set(items) => {
216 items.iter().map(|v| T::from_cql(v)).collect()
217 }
218 _ => Err(ScyllaError::type_conversion("Expected list or set")),
219 }
220 }
221}
222
223impl FromCqlValue for serde_json::Value {
224 fn from_cql(value: &CqlValue) -> ScyllaResult<Self> {
225 let scylla_value: ScyllaValue = value.clone().into();
226 Ok(scylla_value.into())
227 }
228}
229
230impl<T: DeserializeOwned> FromScyllaRow for T {
244 fn from_row(row: &Row) -> ScyllaResult<Self> {
245 let values: Vec<serde_json::Value> = row
250 .columns
251 .iter()
252 .map(|col| {
253 col.as_ref().map_or(serde_json::Value::Null, |v| {
254 let sv: ScyllaValue = v.clone().into();
255 sv.into()
256 })
257 })
258 .collect();
259
260 serde_json::from_value(serde_json::Value::Array(values))
261 .map_err(|e| ScyllaError::deserialization(e.to_string()))
262 }
263}
264
265#[macro_export]
277macro_rules! impl_from_row {
278 ($name:ident { $($field:ident: $ty:ty),* $(,)? }) => {
279 impl $crate::row::FromScyllaRow for $name {
280 fn from_row(row: &scylla::frame::response::result::Row) -> $crate::error::ScyllaResult<Self> {
281 let accessor = $crate::row::RowAccessor::new(row);
282 let mut idx = 0;
283 Ok(Self {
284 $(
285 $field: {
286 let val = accessor.get::<$ty>(idx)?;
287 idx += 1;
288 val
289 },
290 )*
291 })
292 }
293 }
294 };
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_from_cql_primitives() {
303 assert!(bool::from_cql(&CqlValue::Boolean(true)).unwrap());
304 assert_eq!(i32::from_cql(&CqlValue::Int(42)).unwrap(), 42);
305 assert_eq!(i64::from_cql(&CqlValue::BigInt(100)).unwrap(), 100);
306 assert!((f64::from_cql(&CqlValue::Double(3.14)).unwrap() - 3.14).abs() < f64::EPSILON);
307 assert_eq!(
308 String::from_cql(&CqlValue::Text("hello".into())).unwrap(),
309 "hello"
310 );
311 }
312
313 #[test]
314 fn test_from_cql_optional() {
315 let result: Option<i32> = Option::<i32>::from_cql(&CqlValue::Int(42)).unwrap();
316 assert_eq!(result, Some(42));
317
318 let result: Option<i32> = Option::<i32>::from_cql(&CqlValue::Empty).unwrap();
319 assert_eq!(result, None);
320 }
321
322 #[test]
323 fn test_from_cql_list() {
324 let list = CqlValue::List(vec![CqlValue::Int(1), CqlValue::Int(2), CqlValue::Int(3)]);
325 let result: Vec<i32> = Vec::<i32>::from_cql(&list).unwrap();
326 assert_eq!(result, vec![1, 2, 3]);
327 }
328
329 #[test]
330 fn test_from_cql_date_round_trip() {
331 use chrono::NaiveDate;
332 use scylla::frame::value::CqlDate;
333
334 let epoch = NaiveDate::from_cql(&CqlValue::Date(CqlDate(1u32 << 31))).unwrap();
337 assert_eq!(epoch, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap());
338
339 let modern = NaiveDate::from_cql(&CqlValue::Date(CqlDate((1u32 << 31) + 10_957))).unwrap();
341 assert_eq!(modern, NaiveDate::from_ymd_opt(2000, 1, 1).unwrap());
342
343 let past = NaiveDate::from_cql(&CqlValue::Date(CqlDate((1u32 << 31) - 25_567))).unwrap();
345 assert_eq!(past, NaiveDate::from_ymd_opt(1900, 1, 1).unwrap());
346 }
347
348 #[test]
349 fn test_from_cql_date_out_of_range() {
350 use chrono::NaiveDate;
351 use scylla::frame::value::CqlDate;
352
353 assert!(NaiveDate::from_cql(&CqlValue::Date(CqlDate(0))).is_err());
355 }
356
357 #[test]
358 fn test_from_scylla_row_named_struct() {
359 #[derive(Debug, PartialEq, serde::Deserialize)]
360 struct UserRow {
361 id: i32,
362 name: String,
363 email: Option<String>,
364 }
365
366 let row = Row {
368 columns: vec![
369 Some(CqlValue::Int(7)),
370 Some(CqlValue::Text("alice".into())),
371 None,
372 ],
373 };
374
375 let user = UserRow::from_row(&row).unwrap();
376 assert_eq!(
377 user,
378 UserRow {
379 id: 7,
380 name: "alice".to_string(),
381 email: None,
382 }
383 );
384 }
385
386 #[test]
387 fn test_from_scylla_row_tuple_struct() {
388 #[derive(Debug, PartialEq, serde::Deserialize)]
389 struct Pair(i32, String);
390
391 let row = Row {
392 columns: vec![Some(CqlValue::Int(1)), Some(CqlValue::Text("one".into()))],
393 };
394
395 assert_eq!(Pair::from_row(&row).unwrap(), Pair(1, "one".to_string()));
396 }
397}