1use crate::error::DactylError;
23use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
27pub struct Rows(pub Vec<Row>);
28
29impl Rows {
30 pub fn as_slice(&self) -> &[Row] {
32 &self.0
33 }
34
35 pub fn iter(&self) -> std::slice::Iter<'_, Row> {
37 self.0.iter()
38 }
39
40 pub fn len(&self) -> usize {
42 self.0.len()
43 }
44
45 pub fn is_empty(&self) -> bool {
47 self.0.is_empty()
48 }
49}
50
51impl IntoIterator for Rows {
52 type Item = Row;
53 type IntoIter = std::vec::IntoIter<Row>;
54
55 fn into_iter(self) -> Self::IntoIter {
56 self.0.into_iter()
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
66pub struct Row {
67 pub columns: Vec<String>,
73 pub values: Vec<serde_json::Value>,
75}
76
77#[derive(Debug, Clone, PartialEq)]
79pub enum Parameter {
80 Null,
81 Bool(bool),
82 Integer(i64),
83 Real(f64),
84 Text(String),
85 Blob(Vec<u8>),
86}
87
88impl serde::Serialize for Parameter {
89 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
90 where
91 S: serde::Serializer,
92 {
93 match self {
94 Parameter::Null => serializer.serialize_unit(),
95 Parameter::Bool(b) => serializer.serialize_bool(*b),
96 Parameter::Integer(i) => serializer.serialize_i64(*i),
97 Parameter::Real(f) => serializer.serialize_f64(*f),
98 Parameter::Text(s) => serializer.serialize_str(s),
99 Parameter::Blob(bytes) => serializer.serialize_bytes(bytes),
100 }
101 }
102}
103
104impl<'de> serde::Deserialize<'de> for Parameter {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: serde::Deserializer<'de>,
108 {
109 struct ParameterVisitor;
110 impl<'de> serde::de::Visitor<'de> for ParameterVisitor {
111 type Value = Parameter;
112 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
113 formatter.write_str("a database parameter value")
114 }
115 fn visit_none<E>(self) -> Result<Self::Value, E>
116 where
117 E: serde::de::Error,
118 {
119 Ok(Parameter::Null)
120 }
121 fn visit_unit<E>(self) -> Result<Self::Value, E>
122 where
123 E: serde::de::Error,
124 {
125 Ok(Parameter::Null)
126 }
127 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
128 where
129 E: serde::de::Error,
130 {
131 Ok(Parameter::Bool(v))
132 }
133 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
134 where
135 E: serde::de::Error,
136 {
137 Ok(Parameter::Integer(v))
138 }
139 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
140 where
141 E: serde::de::Error,
142 {
143 Ok(Parameter::Integer(v as i64))
144 }
145 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
146 where
147 E: serde::de::Error,
148 {
149 Ok(Parameter::Real(v))
150 }
151 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
152 where
153 E: serde::de::Error,
154 {
155 Ok(Parameter::Text(v.to_string()))
156 }
157 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
158 where
159 E: serde::de::Error,
160 {
161 Ok(Parameter::Text(v))
162 }
163 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
164 where
165 E: serde::de::Error,
166 {
167 Ok(Parameter::Blob(v.to_vec()))
168 }
169 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
170 where
171 E: serde::de::Error,
172 {
173 Ok(Parameter::Blob(v))
174 }
175 }
176 deserializer.deserialize_any(ParameterVisitor)
177 }
178}
179
180impl From<i64> for Parameter {
181 fn from(v: i64) -> Self {
182 Parameter::Integer(v)
183 }
184}
185impl From<i32> for Parameter {
186 fn from(v: i32) -> Self {
187 Parameter::Integer(v as i64)
188 }
189}
190impl From<u32> for Parameter {
191 fn from(v: u32) -> Self {
192 Parameter::Integer(v as i64)
193 }
194}
195impl From<usize> for Parameter {
196 fn from(v: usize) -> Self {
197 Parameter::Integer(v as i64)
198 }
199}
200impl From<bool> for Parameter {
201 fn from(v: bool) -> Self {
202 Parameter::Bool(v)
203 }
204}
205impl From<f64> for Parameter {
206 fn from(v: f64) -> Self {
207 Parameter::Real(v)
208 }
209}
210impl From<String> for Parameter {
211 fn from(v: String) -> Self {
212 Parameter::Text(v)
213 }
214}
215impl From<&str> for Parameter {
216 fn from(v: &str) -> Self {
217 Parameter::Text(v.to_string())
218 }
219}
220
221impl From<Vec<u8>> for Parameter {
222 fn from(v: Vec<u8>) -> Self {
223 Parameter::Blob(v)
224 }
225}
226
227impl From<Option<String>> for Parameter {
228 fn from(v: Option<String>) -> Self {
229 match v {
230 Some(s) => Parameter::Text(s),
231 None => Parameter::Null,
232 }
233 }
234}
235
236impl From<Option<&str>> for Parameter {
237 fn from(v: Option<&str>) -> Self {
238 match v {
239 Some(s) => Parameter::Text(s.to_string()),
240 None => Parameter::Null,
241 }
242 }
243}
244
245impl From<Option<i64>> for Parameter {
246 fn from(v: Option<i64>) -> Self {
247 match v {
248 Some(i) => Parameter::Integer(i),
249 None => Parameter::Null,
250 }
251 }
252}
253
254impl From<Option<bool>> for Parameter {
255 fn from(v: Option<bool>) -> Self {
256 match v {
257 Some(b) => Parameter::Bool(b),
258 None => Parameter::Null,
259 }
260 }
261}
262
263pub trait RowIndex: std::fmt::Debug {
268 fn idx(&self, row: &Row) -> Option<usize>;
270}
271
272impl RowIndex for usize {
273 fn idx(&self, _row: &Row) -> Option<usize> {
274 Some(*self)
275 }
276}
277
278impl RowIndex for &str {
279 fn idx(&self, row: &Row) -> Option<usize> {
280 row.columns.iter().position(|c| c == self)
282 }
283}
284
285impl RowIndex for String {
286 fn idx(&self, row: &Row) -> Option<usize> {
287 row.columns.iter().position(|c| c == self)
288 }
289}
290
291impl Row {
292 pub fn get<I: RowIndex, T: serde::de::DeserializeOwned>(
305 &self,
306 index: I,
307 ) -> Result<T, DactylError> {
308 let i = self.idx(&index)?;
309 let val = &self.values[i];
310 serde_json::from_value(val.clone()).map_err(|e| {
311 if val.is_null() {
312 DactylError::Conversion(format!(
313 "column {:?} is NULL; use Option<T> with get for nullable columns",
314 index
315 ))
316 } else {
317 DactylError::Conversion(format!(
318 "failed to convert column {:?} to target type: {}",
319 index, e
320 ))
321 }
322 })
323 }
324
325 pub fn try_get<I: RowIndex, T: serde::de::DeserializeOwned>(
327 &self,
328 index: I,
329 ) -> Result<T, DactylError> {
330 self.get(index)
331 }
332
333 pub fn is_null<I: RowIndex>(&self, index: I) -> Result<bool, DactylError> {
337 let i = self.idx(&index)?;
338 Ok(self.values[i].is_null())
339 }
340
341 pub fn get_bool<I: RowIndex>(&self, index: I) -> Result<bool, DactylError> {
345 let i = self.idx(&index)?;
346 match &self.values[i] {
347 serde_json::Value::Null => Err(Self::null_err(&index)),
348 serde_json::Value::Bool(b) => Ok(*b),
349 serde_json::Value::Number(n) if n.as_i64() == Some(0) => Ok(false),
350 serde_json::Value::Number(n) if n.as_i64() == Some(1) => Ok(true),
351 other => Err(DactylError::Conversion(format!(
352 "cannot read {other:?} as bool at column {:?}",
353 index
354 ))),
355 }
356 }
357
358 pub fn get_int<I: RowIndex>(&self, index: I) -> Result<i64, DactylError> {
362 let i = self.idx(&index)?;
363 match &self.values[i] {
364 serde_json::Value::Null => Err(Self::null_err(&index)),
365 serde_json::Value::Number(n) => n.as_i64().ok_or_else(|| {
366 DactylError::Conversion(format!("value is not i64 at column {:?}", index))
367 }),
368 other => Err(DactylError::Conversion(format!(
369 "cannot read {other:?} as i64 at column {:?}",
370 index
371 ))),
372 }
373 }
374
375 pub fn get_real<I: RowIndex>(&self, index: I) -> Result<f64, DactylError> {
379 let i = self.idx(&index)?;
380 match &self.values[i] {
381 serde_json::Value::Null => Err(Self::null_err(&index)),
382 serde_json::Value::Number(n) => n.as_f64().ok_or_else(|| {
383 DactylError::Conversion(format!("value is not f64 at column {:?}", index))
384 }),
385 other => Err(DactylError::Conversion(format!(
386 "cannot read {other:?} as f64 at column {:?}",
387 index
388 ))),
389 }
390 }
391
392 pub fn get_str<I: RowIndex>(&self, index: I) -> Result<String, DactylError> {
396 self.get_str_ref(index).map(str::to_owned)
397 }
398
399 pub fn get_str_ref<I: RowIndex>(&self, index: I) -> Result<&str, DactylError> {
403 let i = self.idx(&index)?;
404 match &self.values[i] {
405 serde_json::Value::Null => Err(Self::null_err(&index)),
406 serde_json::Value::String(s) => Ok(s.as_str()),
407 other => Err(DactylError::Conversion(format!(
408 "cannot read {other:?} as str at column {:?}",
409 index
410 ))),
411 }
412 }
413
414 pub fn get_json<I: RowIndex>(&self, index: I) -> Result<serde_json::Value, DactylError> {
416 self.get_json_ref(index).cloned()
417 }
418
419 pub fn get_json_ref<I: RowIndex>(&self, index: I) -> Result<&serde_json::Value, DactylError> {
421 let i = self.idx(&index)?;
422 Ok(&self.values[i])
423 }
424
425 fn null_err<I: std::fmt::Debug>(index: &I) -> DactylError {
426 DactylError::Conversion(format!(
427 "column {:?} is NULL; use Option<T> with get for nullable columns",
428 index
429 ))
430 }
431
432 fn idx<I: RowIndex>(&self, index: &I) -> Result<usize, DactylError> {
433 index
434 .idx(self)
435 .ok_or_else(|| DactylError::ColumnNotFound(format!("{:?}", index)))
436 .and_then(|i| {
437 if i < self.values.len() {
438 Ok(i)
439 } else {
440 Err(DactylError::ColumnNotFound(format!(
441 "index {i} out of bounds"
442 )))
443 }
444 })
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use serde_json::json;
452
453 fn sample_row() -> Row {
454 Row {
455 columns: vec![
456 "id".into(),
457 "flag".into(),
458 "ratio".into(),
459 "label".into(),
460 "payload".into(),
461 "nullable".into(),
462 "name".into(), "name".into(), ],
465 values: vec![
466 json!(42),
467 json!(true),
468 json!(1.5),
469 json!("hello"),
470 json!({"k": 1}),
471 json!(null),
472 json!("first"),
473 json!("second"),
474 ],
475 }
476 }
477
478 #[test]
479 fn scalar_matrix_strict_and_lenient() {
480 let row = sample_row();
481
482 assert_eq!(row.get::<_, i64>("id").unwrap(), 42);
483 assert_eq!(row.try_get::<_, i64>("id").unwrap(), 42);
484 assert_eq!(row.get_int("id").unwrap(), 42);
485 assert_eq!(row.get_real("id").unwrap(), 42.0);
486
487 assert!(row.get_bool("flag").unwrap());
488 assert!(row.get::<_, bool>("flag").unwrap());
489
490 assert_eq!(row.get::<_, f64>("ratio").unwrap(), 1.5);
491 assert_eq!(row.get_real("ratio").unwrap(), 1.5);
492 assert!(matches!(
493 row.get_int("ratio"),
494 Err(DactylError::Conversion(_))
495 ));
496
497 assert_eq!(row.get_str("label").unwrap(), "hello");
498 assert_eq!(row.get_str_ref("label").unwrap(), "hello");
499 assert_eq!(row.get::<_, String>("label").unwrap(), "hello");
500
501 let payload = row.get_json("payload").unwrap();
502 assert_eq!(payload, json!({"k": 1}));
503 assert_eq!(row.get_json_ref("payload").unwrap(), &json!({"k": 1}));
504 #[derive(Deserialize)]
505 struct Payload {
506 k: i64,
507 }
508 assert_eq!(row.get::<_, Payload>("payload").unwrap().k, 1);
509 }
510
511 #[test]
512 fn sqlite_style_bool_as_integer() {
513 let row = Row {
514 columns: vec!["flag".into(), "off".into()],
515 values: vec![json!(1), json!(0)],
516 };
517 assert!(row.get_bool("flag").unwrap());
518 assert!(!row.get_bool("off").unwrap());
519 assert!(matches!(
521 row.get::<_, bool>("flag"),
522 Err(DactylError::Conversion(_))
523 ));
524 }
525
526 #[test]
527 fn null_semantics() {
528 let row = sample_row();
529 assert!(row.is_null("nullable").unwrap());
530 assert!(!row.is_null("id").unwrap());
531 assert!(row.get::<_, Option<i64>>("nullable").unwrap().is_none());
532 assert!(row.get::<_, Option<String>>("nullable").unwrap().is_none());
533
534 let null_errs: Vec<DactylError> = vec![
535 row.get::<_, i64>("nullable").unwrap_err(),
536 row.get_int("nullable").unwrap_err(),
537 row.get_bool("nullable").unwrap_err(),
538 row.get_real("nullable").unwrap_err(),
539 row.get_str("nullable").unwrap_err(),
540 row.get_str_ref("nullable").unwrap_err(),
541 ];
542 for err in null_errs {
543 match err {
544 DactylError::Conversion(msg) => {
545 assert!(msg.contains("NULL"), "expected NULL hint, got {msg}");
546 }
547 other => panic!("expected Conversion for NULL, got {other:?}"),
548 }
549 }
550
551 assert_eq!(row.get_json("nullable").unwrap(), json!(null));
552 assert!(row.get_json_ref("nullable").unwrap().is_null());
553 }
554
555 #[test]
556 fn missing_column_is_column_not_found() {
557 let row = sample_row();
558 assert!(matches!(
559 row.get::<_, i64>("nope"),
560 Err(DactylError::ColumnNotFound(_))
561 ));
562 assert!(matches!(
563 row.get_int("nope"),
564 Err(DactylError::ColumnNotFound(_))
565 ));
566 assert!(matches!(
567 row.is_null("nope"),
568 Err(DactylError::ColumnNotFound(_))
569 ));
570 assert!(matches!(
571 row.get_json_ref("nope"),
572 Err(DactylError::ColumnNotFound(_))
573 ));
574 assert!(matches!(
575 row.get::<_, i64>(99usize),
576 Err(DactylError::ColumnNotFound(_))
577 ));
578 }
579
580 #[test]
581 fn conversion_failures() {
582 let row = sample_row();
583 assert!(matches!(
584 row.get::<_, bool>("label"),
585 Err(DactylError::Conversion(_))
586 ));
587 assert!(matches!(
588 row.get_int("label"),
589 Err(DactylError::Conversion(_))
590 ));
591 assert!(matches!(row.get_str("id"), Err(DactylError::Conversion(_))));
592 assert!(matches!(
593 row.get_bool("ratio"),
594 Err(DactylError::Conversion(_))
595 ));
596 }
597
598 #[test]
599 fn duplicate_alias_first_match_and_positional() {
600 let row = sample_row();
601 assert_eq!(row.get_str("name").unwrap(), "first");
603 assert_eq!(row.get_str_ref("name").unwrap(), "first");
604 assert_eq!(row.get_int(0usize).unwrap(), 42);
605 let second_name_idx = row
607 .columns
608 .iter()
609 .enumerate()
610 .filter(|(_, c)| *c == "name")
611 .nth(1)
612 .map(|(i, _)| i)
613 .expect("second name");
614 assert_eq!(row.get_str(second_name_idx).unwrap(), "second");
615 assert_eq!(row.get_json_ref(second_name_idx).unwrap(), &json!("second"));
616 }
617
618 #[test]
619 fn borrowed_values_tied_to_row_lifetime() {
620 let row = sample_row();
621 let s: &str = row.get_str_ref("label").unwrap();
622 let j: &serde_json::Value = row.get_json_ref("payload").unwrap();
623 assert_eq!(s, "hello");
626 assert_eq!(j["k"], 1);
627 let owned = row.get_str("label").unwrap();
629 drop(row);
630 assert_eq!(owned, "hello");
631 }
632}