sqlite_tiny/api/
row.rs

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