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 => write!(f, "null"),
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%.3f%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
263#[derive(Debug, Clone)]
265pub struct CqlResult {
266 pub columns: Vec<CqlColumn>,
268 pub rows: Vec<CqlRow>,
270 pub has_rows: bool,
272 pub tracing_id: Option<uuid::Uuid>,
274 pub warnings: Vec<String>,
276}
277
278impl CqlResult {
279 pub fn empty() -> Self {
281 Self {
282 columns: Vec::new(),
283 rows: Vec::new(),
284 has_rows: false,
285 tracing_id: None,
286 warnings: Vec::new(),
287 }
288 }
289
290 pub fn row_count(&self) -> usize {
292 self.rows.len()
293 }
294
295 pub fn column_count(&self) -> usize {
297 self.columns.len()
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn cql_value_display_text() {
307 assert_eq!(CqlValue::Text("hello".to_string()).to_string(), "hello");
308 }
309
310 #[test]
311 fn cql_value_display_boolean() {
312 assert_eq!(CqlValue::Boolean(true).to_string(), "True");
313 assert_eq!(CqlValue::Boolean(false).to_string(), "False");
314 }
315
316 #[test]
317 fn cql_value_display_int() {
318 assert_eq!(CqlValue::Int(42).to_string(), "42");
319 assert_eq!(CqlValue::BigInt(-100).to_string(), "-100");
320 assert_eq!(CqlValue::SmallInt(7).to_string(), "7");
321 assert_eq!(CqlValue::TinyInt(-1).to_string(), "-1");
322 }
323
324 #[test]
325 fn cql_value_display_blob() {
326 assert_eq!(
327 CqlValue::Blob(vec![0xde, 0xad, 0xbe, 0xef]).to_string(),
328 "0xdeadbeef"
329 );
330 }
331
332 #[test]
333 fn cql_value_display_uuid() {
334 let id = Uuid::nil();
335 assert_eq!(
336 CqlValue::Uuid(id).to_string(),
337 "00000000-0000-0000-0000-000000000000"
338 );
339 }
340
341 #[test]
342 fn cql_value_display_null() {
343 assert_eq!(CqlValue::Null.to_string(), "null");
344 }
345
346 #[test]
347 fn cql_value_display_list() {
348 let list = CqlValue::List(vec![CqlValue::Int(1), CqlValue::Int(2), CqlValue::Int(3)]);
349 assert_eq!(list.to_string(), "[1, 2, 3]");
350 }
351
352 #[test]
353 fn cql_value_display_set() {
354 let set = CqlValue::Set(vec![
355 CqlValue::Text("a".to_string()),
356 CqlValue::Text("b".to_string()),
357 ]);
358 assert_eq!(set.to_string(), "{'a', 'b'}");
359 }
360
361 #[test]
362 fn cql_value_display_map() {
363 let map = CqlValue::Map(vec![(CqlValue::Text("key".to_string()), CqlValue::Int(42))]);
364 assert_eq!(map.to_string(), "{'key': 42}");
365 }
366
367 #[test]
368 fn cql_value_display_tuple() {
369 let tuple = CqlValue::Tuple(vec![
370 Some(CqlValue::Int(1)),
371 None,
372 Some(CqlValue::Text("x".to_string())),
373 ]);
374 assert_eq!(tuple.to_string(), "(1, null, 'x')");
375 }
376
377 #[test]
378 fn cql_value_display_udt() {
379 let udt = CqlValue::UserDefinedType {
380 keyspace: "ks".to_string(),
381 type_name: "my_type".to_string(),
382 fields: vec![
383 (
384 "name".to_string(),
385 Some(CqlValue::Text("Alice".to_string())),
386 ),
387 ("age".to_string(), Some(CqlValue::Int(30))),
388 ],
389 };
390 assert_eq!(udt.to_string(), "{name: 'Alice', age: 30}");
391 }
392
393 #[test]
394 fn cql_value_display_float_special() {
395 assert_eq!(CqlValue::Float(f32::NAN).to_string(), "NaN");
396 assert_eq!(CqlValue::Float(f32::INFINITY).to_string(), "Infinity");
397 assert_eq!(CqlValue::Float(f32::NEG_INFINITY).to_string(), "-Infinity");
398 assert_eq!(CqlValue::Double(f64::NAN).to_string(), "NaN");
399 }
400
401 #[test]
402 fn cql_result_empty() {
403 let result = CqlResult::empty();
404 assert!(!result.has_rows);
405 assert_eq!(result.row_count(), 0);
406 assert_eq!(result.column_count(), 0);
407 }
408
409 #[test]
410 fn cql_row_get_by_name() {
411 let columns = vec![
412 CqlColumn {
413 name: "id".to_string(),
414 type_name: "int".to_string(),
415 },
416 CqlColumn {
417 name: "name".to_string(),
418 type_name: "text".to_string(),
419 },
420 ];
421 let row = CqlRow {
422 values: vec![CqlValue::Int(1), CqlValue::Text("Alice".to_string())],
423 };
424 assert_eq!(
425 row.get_by_name("name", &columns),
426 Some(&CqlValue::Text("Alice".to_string()))
427 );
428 assert_eq!(row.get_by_name("missing", &columns), None);
429 }
430}