sqlite_tiny/api/
row.rs

1//! An SQLite query result row
2
3use crate::api::ffiext::PointerMutFlex;
4use crate::api::types::SqliteType;
5use crate::error::Error;
6use crate::{err, ffi};
7use std::ffi::CStr;
8
9/// An SQLite result row
10#[derive(Debug)]
11pub struct Row<'stmt> {
12    /// The statement
13    pub(in crate::api) raw: PointerMutFlex<'stmt, ffi::sqlite3_stmt>,
14}
15impl Row<'_> {
16    /// The amount of fields/columns in the current row
17    #[allow(clippy::missing_panics_doc, reason = "Panic should never occur during normal operation")]
18    pub fn len(&self) -> usize {
19        let columns = unsafe { ffi::sqlite3_data_count(self.raw.as_ptr()) };
20        // Note: If the amount of columns is greater than `usize::MAX` or an `core::ffi::c_int` is greater than
21        //  `usize::MAX`, something is super weird here and we want to panic
22        #[allow(clippy::expect_used, reason = "Panic should never occur during normal operation")]
23        usize::try_from(columns).expect("amount of columns is greater than `usize::MAX`")
24    }
25    /// Whether the current row contains some fields/columns or not
26    #[must_use]
27    pub fn is_empty(&self) -> bool {
28        self.len() == 0
29    }
30
31    /// Reads the value for the requested column from the current row
32    ///
33    /// # Note
34    /// Column indices for reading start with `0`
35    pub fn read<T>(&self, column: std::ffi::c_int) -> Result<T, Error>
36    where
37        SqliteType: TryInto<T>,
38        <SqliteType as TryInto<T>>::Error: std::error::Error + Send + 'static,
39    {
40        // Get the type and read the value as said type
41        let type_ = unsafe { ffi::sqlite3_column_type(self.raw.as_ptr(), column) };
42        let value = match type_ {
43            ffi::SQLITE_NULL => SqliteType::Null,
44            ffi::SQLITE_INTEGER => self.read_integer(column)?,
45            ffi::SQLITE_FLOAT => self.read_real(column)?,
46            ffi::SQLITE_TEXT => self.read_text(column)?,
47            ffi::SQLITE_BLOB => self.read_blob(column)?,
48            _ => return Err(err!("Unknown SQLite column type: {type_}")),
49        };
50
51        // Convert value into requested type
52        value.try_into().map_err(|e| err!(with: e, "Failed to load from SQLite type"))
53    }
54    /// Reads an INTEGER value from the given column
55    fn read_integer(&self, column: std::ffi::c_int) -> Result<SqliteType, Error> {
56        let value = unsafe { ffi::sqlite3_column_int64(self.raw.as_ptr(), column) };
57        Ok(SqliteType::Integer(value))
58    }
59    /// Reads a REAL value from the given column
60    fn read_real(&self, column: std::ffi::c_int) -> Result<SqliteType, Error> {
61        let value = unsafe { ffi::sqlite3_column_double(self.raw.as_ptr(), column) };
62        Ok(SqliteType::Real(value))
63    }
64    /// Reads a TEXT value from the given column
65    fn read_text(&self, column: std::ffi::c_int) -> Result<SqliteType, Error> {
66        // Get text value
67        let chars = unsafe { ffi::sqlite3_column_text(self.raw.as_ptr(), column) };
68        let text = unsafe { CStr::from_ptr(chars as _) };
69
70        // Get rust string
71        let text = text.to_str().map_err(|e| err!(with: e, "SQLite string is not valid UTF-8"))?;
72        Ok(SqliteType::Text(text.to_string()))
73    }
74    /// Reads a BLOB value from the given column
75    fn read_blob(&self, column: std::ffi::c_int) -> Result<SqliteType, Error> {
76        // Get blob value
77        let data = unsafe { ffi::sqlite3_column_blob(self.raw.as_ptr(), column) };
78        let false = data.is_null() else {
79            // SQLite has a "special" way of handling empty blobs
80            return Ok(SqliteType::Blob(Vec::new()));
81        };
82
83        // Get blob length and copy bytes
84        let len = unsafe { ffi::sqlite3_column_bytes(self.raw.as_ptr(), column) };
85        let bytes = unsafe { std::slice::from_raw_parts(data as *const u8, len as usize) };
86        Ok(SqliteType::Blob(bytes.to_vec()))
87    }
88}