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 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
176 where
177 A: serde::de::SeqAccess<'de>,
178 {
179 let mut bytes = Vec::new();
180 while let Some(value) = seq.next_element::<u64>()? {
181 let byte = u8::try_from(value).map_err(|_| {
182 serde::de::Error::custom("blob parameter values must fit in a byte")
183 })?;
184 bytes.push(byte);
185 }
186 Ok(Parameter::Blob(bytes))
187 }
188 }
189 deserializer.deserialize_any(ParameterVisitor)
190 }
191}
192
193impl From<i64> for Parameter {
194 fn from(v: i64) -> Self {
195 Parameter::Integer(v)
196 }
197}
198impl From<i32> for Parameter {
199 fn from(v: i32) -> Self {
200 Parameter::Integer(v as i64)
201 }
202}
203impl From<u32> for Parameter {
204 fn from(v: u32) -> Self {
205 Parameter::Integer(v as i64)
206 }
207}
208impl From<usize> for Parameter {
209 fn from(v: usize) -> Self {
210 Parameter::Integer(v as i64)
211 }
212}
213impl From<bool> for Parameter {
214 fn from(v: bool) -> Self {
215 Parameter::Bool(v)
216 }
217}
218impl From<f64> for Parameter {
219 fn from(v: f64) -> Self {
220 Parameter::Real(v)
221 }
222}
223impl From<String> for Parameter {
224 fn from(v: String) -> Self {
225 Parameter::Text(v)
226 }
227}
228impl From<&str> for Parameter {
229 fn from(v: &str) -> Self {
230 Parameter::Text(v.to_string())
231 }
232}
233
234impl From<Vec<u8>> for Parameter {
235 fn from(v: Vec<u8>) -> Self {
236 Parameter::Blob(v)
237 }
238}
239
240impl From<Option<String>> for Parameter {
241 fn from(v: Option<String>) -> Self {
242 match v {
243 Some(s) => Parameter::Text(s),
244 None => Parameter::Null,
245 }
246 }
247}
248
249impl From<Option<&str>> for Parameter {
250 fn from(v: Option<&str>) -> Self {
251 match v {
252 Some(s) => Parameter::Text(s.to_string()),
253 None => Parameter::Null,
254 }
255 }
256}
257
258impl From<Option<i64>> for Parameter {
259 fn from(v: Option<i64>) -> Self {
260 match v {
261 Some(i) => Parameter::Integer(i),
262 None => Parameter::Null,
263 }
264 }
265}
266
267impl From<Option<bool>> for Parameter {
268 fn from(v: Option<bool>) -> Self {
269 match v {
270 Some(b) => Parameter::Bool(b),
271 None => Parameter::Null,
272 }
273 }
274}
275
276pub trait RowIndex: std::fmt::Debug {
281 fn idx(&self, row: &Row) -> Option<usize>;
283}
284
285impl RowIndex for usize {
286 fn idx(&self, _row: &Row) -> Option<usize> {
287 Some(*self)
288 }
289}
290
291impl RowIndex for &str {
292 fn idx(&self, row: &Row) -> Option<usize> {
293 row.columns.iter().position(|c| c == self)
295 }
296}
297
298impl RowIndex for String {
299 fn idx(&self, row: &Row) -> Option<usize> {
300 row.columns.iter().position(|c| c == self)
301 }
302}
303
304impl Row {
305 pub fn get<I: RowIndex, T: serde::de::DeserializeOwned>(
318 &self,
319 index: I,
320 ) -> Result<T, DactylError> {
321 let i = self.idx(&index)?;
322 let val = &self.values[i];
323 serde_json::from_value(val.clone()).map_err(|e| {
324 if val.is_null() {
325 DactylError::Conversion(format!(
326 "column {:?} is NULL; use Option<T> with get for nullable columns",
327 index
328 ))
329 } else {
330 DactylError::Conversion(format!(
331 "failed to convert column {:?} to target type: {}",
332 index, e
333 ))
334 }
335 })
336 }
337
338 pub fn try_get<I: RowIndex, T: serde::de::DeserializeOwned>(
340 &self,
341 index: I,
342 ) -> Result<T, DactylError> {
343 self.get(index)
344 }
345
346 pub fn is_null<I: RowIndex>(&self, index: I) -> Result<bool, DactylError> {
350 let i = self.idx(&index)?;
351 Ok(self.values[i].is_null())
352 }
353
354 pub fn get_bool<I: RowIndex>(&self, index: I) -> Result<bool, DactylError> {
358 let i = self.idx(&index)?;
359 match &self.values[i] {
360 serde_json::Value::Null => Err(Self::null_err(&index)),
361 serde_json::Value::Bool(b) => Ok(*b),
362 serde_json::Value::Number(n) if n.as_i64() == Some(0) => Ok(false),
363 serde_json::Value::Number(n) if n.as_i64() == Some(1) => Ok(true),
364 other => Err(DactylError::Conversion(format!(
365 "cannot read {other:?} as bool at column {:?}",
366 index
367 ))),
368 }
369 }
370
371 pub fn get_int<I: RowIndex>(&self, index: I) -> Result<i64, DactylError> {
375 let i = self.idx(&index)?;
376 match &self.values[i] {
377 serde_json::Value::Null => Err(Self::null_err(&index)),
378 serde_json::Value::Number(n) => n.as_i64().ok_or_else(|| {
379 DactylError::Conversion(format!("value is not i64 at column {:?}", index))
380 }),
381 other => Err(DactylError::Conversion(format!(
382 "cannot read {other:?} as i64 at column {:?}",
383 index
384 ))),
385 }
386 }
387
388 pub fn get_real<I: RowIndex>(&self, index: I) -> Result<f64, DactylError> {
392 let i = self.idx(&index)?;
393 match &self.values[i] {
394 serde_json::Value::Null => Err(Self::null_err(&index)),
395 serde_json::Value::Number(n) => n.as_f64().ok_or_else(|| {
396 DactylError::Conversion(format!("value is not f64 at column {:?}", index))
397 }),
398 other => Err(DactylError::Conversion(format!(
399 "cannot read {other:?} as f64 at column {:?}",
400 index
401 ))),
402 }
403 }
404
405 pub fn get_str<I: RowIndex>(&self, index: I) -> Result<String, DactylError> {
409 self.get_str_ref(index).map(str::to_owned)
410 }
411
412 pub fn get_str_ref<I: RowIndex>(&self, index: I) -> Result<&str, DactylError> {
416 let i = self.idx(&index)?;
417 match &self.values[i] {
418 serde_json::Value::Null => Err(Self::null_err(&index)),
419 serde_json::Value::String(s) => Ok(s.as_str()),
420 other => Err(DactylError::Conversion(format!(
421 "cannot read {other:?} as str at column {:?}",
422 index
423 ))),
424 }
425 }
426
427 pub fn get_json<I: RowIndex>(&self, index: I) -> Result<serde_json::Value, DactylError> {
429 self.get_json_ref(index).cloned()
430 }
431
432 pub fn get_json_ref<I: RowIndex>(&self, index: I) -> Result<&serde_json::Value, DactylError> {
434 let i = self.idx(&index)?;
435 Ok(&self.values[i])
436 }
437
438 pub fn get_blob<I: RowIndex>(&self, index: I) -> Result<Vec<u8>, DactylError> {
443 let i = self.idx(&index)?;
444 match &self.values[i] {
445 serde_json::Value::Null => Err(Self::null_err(&index)),
446 serde_json::Value::Array(values) => values
447 .iter()
448 .map(|value| {
449 value
450 .as_u64()
451 .and_then(|byte| u8::try_from(byte).ok())
452 .ok_or_else(|| {
453 DactylError::Conversion(format!(
454 "blob cell at column {:?} is not an array of bytes",
455 index
456 ))
457 })
458 })
459 .collect(),
460 other => Err(DactylError::Conversion(format!(
461 "cannot read {other:?} as blob at column {:?}",
462 index
463 ))),
464 }
465 }
466
467 fn null_err<I: std::fmt::Debug>(index: &I) -> DactylError {
468 DactylError::Conversion(format!(
469 "column {:?} is NULL; use Option<T> with get for nullable columns",
470 index
471 ))
472 }
473
474 fn idx<I: RowIndex>(&self, index: &I) -> Result<usize, DactylError> {
475 index
476 .idx(self)
477 .ok_or_else(|| DactylError::ColumnNotFound(format!("{:?}", index)))
478 .and_then(|i| {
479 if i < self.values.len() {
480 Ok(i)
481 } else {
482 Err(DactylError::ColumnNotFound(format!(
483 "index {i} out of bounds"
484 )))
485 }
486 })
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use serde_json::json;
494
495 fn sample_row() -> Row {
496 Row {
497 columns: vec![
498 "id".into(),
499 "flag".into(),
500 "ratio".into(),
501 "label".into(),
502 "payload".into(),
503 "nullable".into(),
504 "name".into(), "name".into(), ],
507 values: vec![
508 json!(42),
509 json!(true),
510 json!(1.5),
511 json!("hello"),
512 json!({"k": 1}),
513 json!(null),
514 json!("first"),
515 json!("second"),
516 ],
517 }
518 }
519
520 #[test]
521 fn scalar_matrix_strict_and_lenient() {
522 let row = sample_row();
523
524 assert_eq!(row.get::<_, i64>("id").unwrap(), 42);
525 assert_eq!(row.try_get::<_, i64>("id").unwrap(), 42);
526 assert_eq!(row.get_int("id").unwrap(), 42);
527 assert_eq!(row.get_real("id").unwrap(), 42.0);
528
529 assert!(row.get_bool("flag").unwrap());
530 assert!(row.get::<_, bool>("flag").unwrap());
531
532 assert_eq!(row.get::<_, f64>("ratio").unwrap(), 1.5);
533 assert_eq!(row.get_real("ratio").unwrap(), 1.5);
534 assert!(matches!(
535 row.get_int("ratio"),
536 Err(DactylError::Conversion(_))
537 ));
538
539 assert_eq!(row.get_str("label").unwrap(), "hello");
540 assert_eq!(row.get_str_ref("label").unwrap(), "hello");
541 assert_eq!(row.get::<_, String>("label").unwrap(), "hello");
542
543 let payload = row.get_json("payload").unwrap();
544 assert_eq!(payload, json!({"k": 1}));
545 assert_eq!(row.get_json_ref("payload").unwrap(), &json!({"k": 1}));
546 #[derive(Deserialize)]
547 struct Payload {
548 k: i64,
549 }
550 assert_eq!(row.get::<_, Payload>("payload").unwrap().k, 1);
551 }
552
553 #[test]
554 fn sqlite_style_bool_as_integer() {
555 let row = Row {
556 columns: vec!["flag".into(), "off".into()],
557 values: vec![json!(1), json!(0)],
558 };
559 assert!(row.get_bool("flag").unwrap());
560 assert!(!row.get_bool("off").unwrap());
561 assert!(matches!(
563 row.get::<_, bool>("flag"),
564 Err(DactylError::Conversion(_))
565 ));
566 }
567
568 #[test]
569 fn null_semantics() {
570 let row = sample_row();
571 assert!(row.is_null("nullable").unwrap());
572 assert!(!row.is_null("id").unwrap());
573 assert!(row.get::<_, Option<i64>>("nullable").unwrap().is_none());
574 assert!(row.get::<_, Option<String>>("nullable").unwrap().is_none());
575
576 let null_errs: Vec<DactylError> = vec![
577 row.get::<_, i64>("nullable").unwrap_err(),
578 row.get_int("nullable").unwrap_err(),
579 row.get_bool("nullable").unwrap_err(),
580 row.get_real("nullable").unwrap_err(),
581 row.get_str("nullable").unwrap_err(),
582 row.get_str_ref("nullable").unwrap_err(),
583 ];
584 for err in null_errs {
585 match err {
586 DactylError::Conversion(msg) => {
587 assert!(msg.contains("NULL"), "expected NULL hint, got {msg}");
588 }
589 other => panic!("expected Conversion for NULL, got {other:?}"),
590 }
591 }
592
593 assert_eq!(row.get_json("nullable").unwrap(), json!(null));
594 assert!(row.get_json_ref("nullable").unwrap().is_null());
595 }
596
597 #[test]
598 fn missing_column_is_column_not_found() {
599 let row = sample_row();
600 assert!(matches!(
601 row.get::<_, i64>("nope"),
602 Err(DactylError::ColumnNotFound(_))
603 ));
604 assert!(matches!(
605 row.get_int("nope"),
606 Err(DactylError::ColumnNotFound(_))
607 ));
608 assert!(matches!(
609 row.is_null("nope"),
610 Err(DactylError::ColumnNotFound(_))
611 ));
612 assert!(matches!(
613 row.get_json_ref("nope"),
614 Err(DactylError::ColumnNotFound(_))
615 ));
616 assert!(matches!(
617 row.get::<_, i64>(99usize),
618 Err(DactylError::ColumnNotFound(_))
619 ));
620 }
621
622 #[test]
623 fn conversion_failures() {
624 let row = sample_row();
625 assert!(matches!(
626 row.get::<_, bool>("label"),
627 Err(DactylError::Conversion(_))
628 ));
629 assert!(matches!(
630 row.get_int("label"),
631 Err(DactylError::Conversion(_))
632 ));
633 assert!(matches!(row.get_str("id"), Err(DactylError::Conversion(_))));
634 assert!(matches!(
635 row.get_bool("ratio"),
636 Err(DactylError::Conversion(_))
637 ));
638 }
639
640 #[test]
641 fn duplicate_alias_first_match_and_positional() {
642 let row = sample_row();
643 assert_eq!(row.get_str("name").unwrap(), "first");
645 assert_eq!(row.get_str_ref("name").unwrap(), "first");
646 assert_eq!(row.get_int(0usize).unwrap(), 42);
647 let second_name_idx = row
649 .columns
650 .iter()
651 .enumerate()
652 .filter(|(_, c)| *c == "name")
653 .nth(1)
654 .map(|(i, _)| i)
655 .expect("second name");
656 assert_eq!(row.get_str(second_name_idx).unwrap(), "second");
657 assert_eq!(row.get_json_ref(second_name_idx).unwrap(), &json!("second"));
658 }
659
660 #[test]
661 fn blob_parameters_round_trip_through_json_arrays() {
662 let encoded = serde_json::to_value(Parameter::Blob(vec![1, 2, 3])).unwrap();
663 assert_eq!(encoded, json!([1, 2, 3]));
664 assert_eq!(
665 serde_json::from_value::<Parameter>(encoded).unwrap(),
666 Parameter::Blob(vec![1, 2, 3])
667 );
668 let row = Row {
669 columns: vec!["payload".into()],
670 values: vec![json!([1, 2, 3])],
671 };
672 assert_eq!(row.get_blob("payload").unwrap(), vec![1, 2, 3]);
673 }
674
675 #[test]
676 fn borrowed_values_tied_to_row_lifetime() {
677 let row = sample_row();
678 let s: &str = row.get_str_ref("label").unwrap();
679 let j: &serde_json::Value = row.get_json_ref("payload").unwrap();
680 assert_eq!(s, "hello");
683 assert_eq!(j["k"], 1);
684 let owned = row.get_str("label").unwrap();
686 drop(row);
687 assert_eq!(owned, "hello");
688 }
689}