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