Skip to main content

dactyl_db/
rows.rs

1//! Row projection returned by [`crate::read`].
2//!
3//! # Named-column projection contract (dactyl #25)
4//!
5//! Each [`Row`] owns its column names and cell values as JSON. Named lookup is
6//! stable across SQLite and Neon because both adapters normalize cells into
7//! this shape before returning.
8//!
9//! | Concern | Semantics |
10//! |---|---|
11//! | Integer | JSON number that fits `i64` (`get_int` / `get::<i64>`) |
12//! | Real | JSON number (`get_real` / `get::<f64>`; integers are accepted as reals) |
13//! | Boolean | JSON `true`/`false`, or integer `0`/`1` via `get_bool` (SQLite stores bools as integers) |
14//! | Text | JSON string (`get_str` / `get_str_ref` / `get::<String>`) |
15//! | JSON / text | Low-level cell via `get_json` / `get_json_ref`; text payloads stay strings until the caller parses them |
16//! | SQL NULL | JSON `null`. Non-`Option` typed getters return [`DactylError::Conversion`]; `get::<Option<T>>` yields `None`. `is_null` / `get_json` surface null without converting. |
17//! | Missing column | [`DactylError::ColumnNotFound`] |
18//! | Duplicate aliases | Left-to-right **first match**. `select a as x, b as x` resolves `get("x")` to the first `x`. Positional indexes still reach later duplicates. |
19//! | Conversion failure | [`DactylError::Conversion`] with the column key and a reason string |
20//! | Ownership | `get`, `get_*` (except `*_ref`) return **owned** values independent of the row. `get_str_ref` / `get_json_ref` borrow from `&self` for the row lifetime. A `Row` outlives the adapter connection. |
21
22use crate::error::DactylError;
23use serde::{Deserialize, Serialize};
24
25/// A collection of result rows.
26#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
27pub struct Rows(pub Vec<Row>);
28
29impl Rows {
30    /// Borrow the rows as a slice.
31    pub fn as_slice(&self) -> &[Row] {
32        &self.0
33    }
34
35    /// Iterate over rows.
36    pub fn iter(&self) -> std::slice::Iter<'_, Row> {
37        self.0.iter()
38    }
39
40    /// Number of rows.
41    pub fn len(&self) -> usize {
42        self.0.len()
43    }
44
45    /// Whether the result is empty.
46    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/// One result row. Carries the column names plus the per-cell JSON values.
61///
62/// The row **owns** both vectors. After `read` returns, the
63/// short-lived adapter is dropped; callers may keep `Row` values indefinitely.
64/// Borrowed accessors (`get_str_ref`, `get_json_ref`) are tied to `&self` only.
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
66pub struct Row {
67    /// Column names, in the order the adapter emitted them.
68    ///
69    /// Duplicate names are allowed (SQL aliases). Named getters resolve the
70    /// **first** occurrence left-to-right; use a positional [`usize`] index to
71    /// reach a later duplicate.
72    pub columns: Vec<String>,
73    /// Per-cell values, parallel to `columns`.
74    pub values: Vec<serde_json::Value>,
75}
76
77/// A unified database parameter value.
78#[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
276/// Helper trait for row indexing by position or column name.
277///
278/// Named indexes (`&str`, `String`) resolve **left-to-right first match** when
279/// a result set contains duplicate column aliases.
280pub trait RowIndex: std::fmt::Debug {
281    /// Return the resolved index in `row`, or `None` if the name is absent.
282    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        // First match wins for duplicate aliases (documented contract).
294        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    /// Strict typed extraction via `serde` into an **owned** `T`.
306    ///
307    /// - Missing column → [`DactylError::ColumnNotFound`].
308    /// - SQL NULL into non-`Option` `T` → [`DactylError::Conversion`].
309    /// - SQL NULL into `Option<T>` → `Ok(None)`.
310    /// - Type mismatch → [`DactylError::Conversion`].
311    /// - Duplicate aliases → first matching column (left-to-right).
312    ///
313    /// Prefer [`Self::get_bool`] for portable bools: SQLite stores booleans as
314    /// integers `0`/`1`, which strict `get::<bool>` rejects. For lenient
315    /// portable shapes use [`Self::get_bool`] / [`Self::get_int`] /
316    /// [`Self::get_real`] / [`Self::get_str`] / [`Self::get_json`].
317    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    /// Alias for [`Self::get`]. Named for callers who prefer a `try_*` style.
339    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    /// Whether the cell is SQL NULL (`serde_json::Value::Null`).
347    ///
348    /// Missing column → [`DactylError::ColumnNotFound`].
349    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    /// Lenient **owned** `bool`: accepts JSON `true`/`false` or integer `0`/`1`.
355    ///
356    /// NULL → [`DactylError::Conversion`].
357    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    /// Lenient **owned** `i64`: accepts JSON integers (not fractional reals).
372    ///
373    /// NULL → [`DactylError::Conversion`].
374    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    /// Lenient **owned** `f64`: accepts any JSON number (integers and reals).
389    ///
390    /// NULL → [`DactylError::Conversion`].
391    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    /// Lenient **owned** `String`: accepts JSON string (clones the cell).
406    ///
407    /// NULL → [`DactylError::Conversion`]. Prefer [`Self::get_str_ref`] to borrow.
408    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    /// Borrowed `&str` tied to the row lifetime. Accepts JSON string only.
413    ///
414    /// NULL → [`DactylError::Conversion`]. The reference is valid while `self` lives.
415    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    /// Owned clone of the raw JSON cell (including `Null`).
428    pub fn get_json<I: RowIndex>(&self, index: I) -> Result<serde_json::Value, DactylError> {
429        self.get_json_ref(index).cloned()
430    }
431
432    /// Borrowed raw JSON cell tied to the row lifetime (including `Null`).
433    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    /// Canonical blob cell: a JSON array of byte integers `0..=255`.
439    ///
440    /// This is the only blob shape Dactyl stores locally. Callers must not
441    /// interpret a JSON string or object as a blob.
442    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(), // first of duplicate alias pair
505                "name".into(), // second alias — positional only via index
506            ],
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        // Strict serde bool rejects integer encoding.
562        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        // Named lookup: left-to-right first match.
644        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        // Positional index reaches the second "name" column.
648        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        // Still usable while `row` is in scope (compile-time lifetime proof
681        // plus runtime equality).
682        assert_eq!(s, "hello");
683        assert_eq!(j["k"], 1);
684        // Owned getters return independent values.
685        let owned = row.get_str("label").unwrap();
686        drop(row);
687        assert_eq!(owned, "hello");
688    }
689}