1use std::fmt;
8use std::net::IpAddr;
9
10use bigdecimal::BigDecimal;
11use chrono::{NaiveDate, NaiveTime};
12use num_bigint::BigInt;
13use uuid::Uuid;
14
15#[derive(Debug, Clone, PartialEq)]
17pub enum CqlValue {
18 Ascii(String),
20 Boolean(bool),
22 BigInt(i64),
24 Blob(Vec<u8>),
26 Counter(i64),
28 Decimal(BigDecimal),
30 Double(f64),
32 Duration {
34 months: i32,
35 days: i32,
36 nanoseconds: i64,
37 },
38 Float(f32),
40 Int(i32),
42 SmallInt(i16),
44 TinyInt(i8),
46 Timestamp(i64),
48 Uuid(Uuid),
50 TimeUuid(Uuid),
52 Inet(IpAddr),
54 Date(NaiveDate),
56 Time(NaiveTime),
58 Text(String),
60 Varint(BigInt),
62 List(Vec<CqlValue>),
64 Set(Vec<CqlValue>),
66 Map(Vec<(CqlValue, CqlValue)>),
68 Tuple(Vec<Option<CqlValue>>),
70 UserDefinedType {
72 keyspace: String,
73 type_name: String,
74 fields: Vec<(String, Option<CqlValue>)>,
75 },
76 Null,
78 Unset,
80}
81
82impl fmt::Display for CqlValue {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 CqlValue::Ascii(s) | CqlValue::Text(s) => write!(f, "{s}"),
86 CqlValue::Boolean(b) => {
87 if *b {
88 write!(f, "True")
89 } else {
90 write!(f, "False")
91 }
92 }
93 CqlValue::BigInt(v) => write!(f, "{v}"),
94 CqlValue::Blob(bytes) => {
95 write!(f, "0x")?;
96 for b in bytes {
97 write!(f, "{b:02x}")?;
98 }
99 Ok(())
100 }
101 CqlValue::Counter(v) => write!(f, "{v}"),
102 CqlValue::Decimal(v) => write!(f, "{v}"),
103 CqlValue::Double(v) => format_float64(f, *v),
104 CqlValue::Duration {
105 months,
106 days,
107 nanoseconds,
108 } => write!(f, "{months}mo{days}d{nanoseconds}ns"),
109 CqlValue::Float(v) => format_float32(f, *v),
110 CqlValue::Int(v) => write!(f, "{v}"),
111 CqlValue::SmallInt(v) => write!(f, "{v}"),
112 CqlValue::TinyInt(v) => write!(f, "{v}"),
113 CqlValue::Timestamp(millis) => format_timestamp(f, *millis),
114 CqlValue::Uuid(u) | CqlValue::TimeUuid(u) => write!(f, "{u}"),
115 CqlValue::Inet(addr) => write!(f, "{addr}"),
116 CqlValue::Date(d) => write!(f, "{d}"),
117 CqlValue::Time(t) => write!(f, "{t}"),
118 CqlValue::Varint(v) => write!(f, "{v}"),
119 CqlValue::List(items) | CqlValue::Set(items) => {
120 let is_set = matches!(self, CqlValue::Set(_));
121 let (open, close) = if is_set { ('{', '}') } else { ('[', ']') };
122 write!(f, "{open}")?;
123 for (i, item) in items.iter().enumerate() {
124 if i > 0 {
125 write!(f, ", ")?;
126 }
127 write_cql_literal(f, item)?;
128 }
129 write!(f, "{close}")
130 }
131 CqlValue::Map(entries) => {
132 write!(f, "{{")?;
133 for (i, (k, v)) in entries.iter().enumerate() {
134 if i > 0 {
135 write!(f, ", ")?;
136 }
137 write_cql_literal(f, k)?;
138 write!(f, ": ")?;
139 write_cql_literal(f, v)?;
140 }
141 write!(f, "}}")
142 }
143 CqlValue::Tuple(items) => {
144 write!(f, "(")?;
145 for (i, item) in items.iter().enumerate() {
146 if i > 0 {
147 write!(f, ", ")?;
148 }
149 match item {
150 Some(v) => write_cql_literal(f, v)?,
151 None => write!(f, "null")?,
152 }
153 }
154 write!(f, ")")
155 }
156 CqlValue::UserDefinedType { fields, .. } => {
157 write!(f, "{{")?;
158 for (i, (name, value)) in fields.iter().enumerate() {
159 if i > 0 {
160 write!(f, ", ")?;
161 }
162 write!(f, "{name}: ")?;
163 match value {
164 Some(v) => write_cql_literal(f, v)?,
165 None => write!(f, "null")?,
166 }
167 }
168 write!(f, "}}")
169 }
170 CqlValue::Null => Ok(()),
171 CqlValue::Unset => write!(f, "<unset>"),
172 }
173 }
174}
175
176fn write_cql_literal(f: &mut fmt::Formatter<'_>, value: &CqlValue) -> fmt::Result {
178 match value {
179 CqlValue::Ascii(s) | CqlValue::Text(s) => {
180 write!(f, "'{}'", s.replace('\'', "''"))
181 }
182 other => write!(f, "{other}"),
183 }
184}
185
186fn format_float64(f: &mut fmt::Formatter<'_>, v: f64) -> fmt::Result {
188 if v.is_nan() {
189 write!(f, "NaN")
190 } else if v.is_infinite() {
191 if v.is_sign_positive() {
192 write!(f, "Infinity")
193 } else {
194 write!(f, "-Infinity")
195 }
196 } else if v == v.trunc() && v.abs() < 1e15 {
197 write!(f, "{v}")
199 } else {
200 write!(f, "{v}")
201 }
202}
203
204fn format_float32(f: &mut fmt::Formatter<'_>, v: f32) -> fmt::Result {
206 if v.is_nan() {
207 write!(f, "NaN")
208 } else if v.is_infinite() {
209 if v.is_sign_positive() {
210 write!(f, "Infinity")
211 } else {
212 write!(f, "-Infinity")
213 }
214 } else {
215 write!(f, "{v}")
216 }
217}
218
219fn format_timestamp(f: &mut fmt::Formatter<'_>, millis: i64) -> fmt::Result {
221 use chrono::{DateTime, Utc};
222 let dt = DateTime::from_timestamp_millis(millis);
223 match dt {
224 Some(dt) => {
225 let utc: DateTime<Utc> = dt;
226 write!(f, "{}", utc.format("%Y-%m-%d %H:%M:%S%.6f%z"))
227 }
228 None => write!(f, "<invalid timestamp: {millis}>"),
229 }
230}
231
232#[derive(Debug, Clone)]
234pub struct CqlColumn {
235 pub name: String,
237 pub type_name: String,
239}
240
241#[derive(Debug, Clone)]
243pub struct CqlRow {
244 pub values: Vec<CqlValue>,
246}
247
248impl CqlRow {
249 pub fn get(&self, index: usize) -> Option<&CqlValue> {
251 self.values.get(index)
252 }
253
254 pub fn get_by_name<'a>(&'a self, name: &str, columns: &[CqlColumn]) -> Option<&'a CqlValue> {
256 columns
257 .iter()
258 .position(|c| c.name == name)
259 .and_then(|idx| self.values.get(idx))
260 }
261}
262
263use std::pin::Pin;
264
265use futures::Stream;
266
267pub struct CqlRowStream {
272 pub columns: Vec<CqlColumn>,
274 pub rows: Pin<Box<dyn Stream<Item = anyhow::Result<CqlRow>> + Send>>,
276}
277
278#[derive(Debug, Clone)]
280pub struct CqlResult {
281 pub columns: Vec<CqlColumn>,
283 pub rows: Vec<CqlRow>,
285 pub has_rows: bool,
287 pub tracing_id: Option<uuid::Uuid>,
289 pub warnings: Vec<String>,
291}
292
293impl CqlResult {
294 pub fn empty() -> Self {
296 Self {
297 columns: Vec::new(),
298 rows: Vec::new(),
299 has_rows: false,
300 tracing_id: None,
301 warnings: Vec::new(),
302 }
303 }
304
305 pub fn row_count(&self) -> usize {
307 self.rows.len()
308 }
309
310 pub fn column_count(&self) -> usize {
312 self.columns.len()
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn cql_value_display_text() {
322 assert_eq!(CqlValue::Text("hello".to_string()).to_string(), "hello");
323 }
324
325 #[test]
326 fn cql_value_display_boolean() {
327 assert_eq!(CqlValue::Boolean(true).to_string(), "True");
328 assert_eq!(CqlValue::Boolean(false).to_string(), "False");
329 }
330
331 #[test]
332 fn cql_value_display_int() {
333 assert_eq!(CqlValue::Int(42).to_string(), "42");
334 assert_eq!(CqlValue::BigInt(-100).to_string(), "-100");
335 assert_eq!(CqlValue::SmallInt(7).to_string(), "7");
336 assert_eq!(CqlValue::TinyInt(-1).to_string(), "-1");
337 }
338
339 #[test]
340 fn cql_value_display_blob() {
341 assert_eq!(
342 CqlValue::Blob(vec![0xde, 0xad, 0xbe, 0xef]).to_string(),
343 "0xdeadbeef"
344 );
345 }
346
347 #[test]
348 fn cql_value_display_uuid() {
349 let id = Uuid::nil();
350 assert_eq!(
351 CqlValue::Uuid(id).to_string(),
352 "00000000-0000-0000-0000-000000000000"
353 );
354 }
355
356 #[test]
357 fn cql_value_display_null() {
358 assert_eq!(CqlValue::Null.to_string(), "");
359 }
360
361 #[test]
362 fn cql_value_display_list() {
363 let list = CqlValue::List(vec![CqlValue::Int(1), CqlValue::Int(2), CqlValue::Int(3)]);
364 assert_eq!(list.to_string(), "[1, 2, 3]");
365 }
366
367 #[test]
368 fn cql_value_display_set() {
369 let set = CqlValue::Set(vec![
370 CqlValue::Text("a".to_string()),
371 CqlValue::Text("b".to_string()),
372 ]);
373 assert_eq!(set.to_string(), "{'a', 'b'}");
374 }
375
376 #[test]
377 fn cql_value_display_map() {
378 let map = CqlValue::Map(vec![(CqlValue::Text("key".to_string()), CqlValue::Int(42))]);
379 assert_eq!(map.to_string(), "{'key': 42}");
380 }
381
382 #[test]
383 fn cql_value_display_tuple() {
384 let tuple = CqlValue::Tuple(vec![
385 Some(CqlValue::Int(1)),
386 None,
387 Some(CqlValue::Text("x".to_string())),
388 ]);
389 assert_eq!(tuple.to_string(), "(1, null, 'x')");
390 }
391
392 #[test]
393 fn cql_value_display_udt() {
394 let udt = CqlValue::UserDefinedType {
395 keyspace: "ks".to_string(),
396 type_name: "my_type".to_string(),
397 fields: vec![
398 (
399 "name".to_string(),
400 Some(CqlValue::Text("Alice".to_string())),
401 ),
402 ("age".to_string(), Some(CqlValue::Int(30))),
403 ],
404 };
405 assert_eq!(udt.to_string(), "{name: 'Alice', age: 30}");
406 }
407
408 #[test]
409 fn cql_value_display_float_special() {
410 assert_eq!(CqlValue::Float(f32::NAN).to_string(), "NaN");
411 assert_eq!(CqlValue::Float(f32::INFINITY).to_string(), "Infinity");
412 assert_eq!(CqlValue::Float(f32::NEG_INFINITY).to_string(), "-Infinity");
413 assert_eq!(CqlValue::Double(f64::NAN).to_string(), "NaN");
414 }
415
416 #[test]
417 fn cql_result_empty() {
418 let result = CqlResult::empty();
419 assert!(!result.has_rows);
420 assert_eq!(result.row_count(), 0);
421 assert_eq!(result.column_count(), 0);
422 }
423
424 #[test]
425 fn cql_row_get_by_name() {
426 let columns = vec![
427 CqlColumn {
428 name: "id".to_string(),
429 type_name: "int".to_string(),
430 },
431 CqlColumn {
432 name: "name".to_string(),
433 type_name: "text".to_string(),
434 },
435 ];
436 let row = CqlRow {
437 values: vec![CqlValue::Int(1), CqlValue::Text("Alice".to_string())],
438 };
439 assert_eq!(
440 row.get_by_name("name", &columns),
441 Some(&CqlValue::Text("Alice".to_string()))
442 );
443 assert_eq!(row.get_by_name("missing", &columns), None);
444 }
445}