audb-runtime 0.1.11

Runtime library for AuDB database applications with Manifold backend
Documentation
//! Query execution traits
//!
//! This module defines traits for type-safe query execution and result deserialization.

use crate::Database;
use crate::error::Result;
use crate::types::{QueryResult, Row, Value};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use uuid::Uuid;

/// Trait for executing queries with type-safe results
///
/// This trait is implemented by generated query functions to provide
/// type-safe query execution.
///
/// ## Example
///
/// ```ignore
/// // Generated code might look like:
/// struct GetUserQuery {
///     id: Uuid,
/// }
///
/// #[async_trait]
/// impl QueryExecutor for GetUserQuery {
///     type Output = User;
///
///     async fn execute(&self, db: &Database) -> Result<Self::Output> {
///         let result = db.execute_hyperql("SELECT * FROM users WHERE id = :id").await?;
///         let row = result.one()?;
///         User::from_row(row)
///     }
/// }
/// ```
#[async_trait]
pub trait QueryExecutor {
    /// The output type of the query
    type Output;

    /// Execute the query against the database
    async fn execute(&self, db: &Database) -> Result<Self::Output>;
}

/// Trait for deserializing a row into a Rust type
///
/// This trait is implemented by schema types to enable conversion
/// from database rows to strongly-typed structs.
///
/// ## Example
///
/// ```ignore
/// struct User {
///     id: Uuid,
///     name: String,
///     age: i64,
/// }
///
/// impl FromRow for User {
///     fn from_row(row: Row) -> Result<Self> {
///         Ok(User {
///             id: row.get_required("id")?.as_uuid()?,
///             name: row.get_required("name")?.as_str()?.to_string(),
///             age: row.get_required("age")?.as_i64()?,
///         })
///     }
/// }
/// ```
pub trait FromRow: Sized {
    /// Convert a row into this type
    fn from_row(row: Row) -> Result<Self>;

    /// Convert multiple rows into a vector of this type
    fn from_rows(result: QueryResult) -> Result<Vec<Self>> {
        result.into_iter().map(|row| Self::from_row(row)).collect()
    }

    /// Convert a single row result, returning an error if zero or multiple rows
    fn from_single_row(result: QueryResult) -> Result<Self> {
        let row = result.one()?;
        Self::from_row(row)
    }

    /// Convert an optional single row result
    fn from_optional_row(result: QueryResult) -> Result<Option<Self>> {
        match result.optional()? {
            Some(row) => Ok(Some(Self::from_row(row)?)),
            None => Ok(None),
        }
    }
}

// Implement FromRow for primitive types

impl FromRow for bool {
    fn from_row(row: Row) -> Result<Self> {
        // For primitives, assume single column named "value"
        row.get_required("value")?.as_bool()
    }
}

impl FromRow for i64 {
    fn from_row(row: Row) -> Result<Self> {
        row.get_required("value")?.as_i64()
    }
}

impl FromRow for f64 {
    fn from_row(row: Row) -> Result<Self> {
        row.get_required("value")?.as_f64()
    }
}

impl FromRow for String {
    fn from_row(row: Row) -> Result<Self> {
        Ok(row.get_required("value")?.as_str()?.to_string())
    }
}

impl FromRow for Uuid {
    fn from_row(row: Row) -> Result<Self> {
        row.get_required("value")?.as_uuid()
    }
}

impl FromRow for DateTime<Utc> {
    fn from_row(row: Row) -> Result<Self> {
        row.get_required("value")?.as_timestamp()
    }
}

// Implement FromRow for Option<T>
impl<T: FromRow> FromRow for Option<T> {
    fn from_row(row: Row) -> Result<Self> {
        if row.is_empty() {
            return Ok(None);
        }
        Ok(Some(T::from_row(row)?))
    }
}

/// Helper trait for converting Values to specific types
///
/// This is used internally by FromRow implementations.
pub trait FromValue: Sized {
    /// Convert a Value to this type
    fn from_value(value: &Value) -> Result<Self>;
}

impl FromValue for bool {
    fn from_value(value: &Value) -> Result<Self> {
        value.as_bool()
    }
}

impl FromValue for i64 {
    fn from_value(value: &Value) -> Result<Self> {
        value.as_i64()
    }
}

impl FromValue for f64 {
    fn from_value(value: &Value) -> Result<Self> {
        value.as_f64()
    }
}

impl FromValue for String {
    fn from_value(value: &Value) -> Result<Self> {
        Ok(value.as_str()?.to_string())
    }
}

impl FromValue for Uuid {
    fn from_value(value: &Value) -> Result<Self> {
        value.as_uuid()
    }
}

impl FromValue for DateTime<Utc> {
    fn from_value(value: &Value) -> Result<Self> {
        value.as_timestamp()
    }
}

impl<T: FromValue> FromValue for Option<T> {
    fn from_value(value: &Value) -> Result<Self> {
        if value.is_null() {
            return Ok(None);
        }
        Ok(Some(T::from_value(value)?))
    }
}

impl<T: FromValue> FromValue for Vec<T> {
    fn from_value(value: &Value) -> Result<Self> {
        let array = value.as_array()?;
        array.iter().map(|v| T::from_value(v)).collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_from_row_primitives() {
        let mut row = Row::new();
        row.insert("value".to_string(), Value::Bool(true));
        assert_eq!(bool::from_row(row).unwrap(), true);

        let mut row = Row::new();
        row.insert("value".to_string(), Value::Integer(42));
        assert_eq!(i64::from_row(row).unwrap(), 42);

        let mut row = Row::new();
        row.insert("value".to_string(), Value::Float(3.14));
        assert!((f64::from_row(row).unwrap() - 3.14).abs() < 0.01);

        let mut row = Row::new();
        row.insert("value".to_string(), Value::String("hello".to_string()));
        assert_eq!(String::from_row(row).unwrap(), "hello");
    }

    #[test]
    fn test_from_row_uuid() {
        let uuid = Uuid::new_v4();
        let mut row = Row::new();
        row.insert("value".to_string(), Value::Uuid(uuid));
        assert_eq!(Uuid::from_row(row).unwrap(), uuid);
    }

    #[test]
    fn test_from_row_timestamp() {
        let now = Utc::now();
        let mut row = Row::new();
        row.insert("value".to_string(), Value::Timestamp(now));
        assert_eq!(DateTime::<Utc>::from_row(row).unwrap(), now);
    }

    #[test]
    fn test_from_row_option() {
        let empty_row = Row::new();
        let result: Option<String> = Option::from_row(empty_row).unwrap();
        assert_eq!(result, None);

        let mut row = Row::new();
        row.insert("value".to_string(), Value::String("hello".to_string()));
        let result: Option<String> = Option::from_row(row).unwrap();
        assert_eq!(result, Some("hello".to_string()));
    }

    #[test]
    fn test_from_rows() {
        let mut row1 = Row::new();
        row1.insert("value".to_string(), Value::Integer(1));
        let mut row2 = Row::new();
        row2.insert("value".to_string(), Value::Integer(2));

        let result = QueryResult::with_rows(vec![row1, row2]);
        let values = i64::from_rows(result).unwrap();
        assert_eq!(values, vec![1, 2]);
    }

    #[test]
    fn test_from_single_row() {
        let mut row = Row::new();
        row.insert("value".to_string(), Value::Integer(42));

        let result = QueryResult::with_rows(vec![row]);
        let value = i64::from_single_row(result).unwrap();
        assert_eq!(value, 42);

        // Test error cases
        let empty = QueryResult::new();
        assert!(i64::from_single_row(empty).is_err());

        let mut row1 = Row::new();
        row1.insert("value".to_string(), Value::Integer(1));
        let mut row2 = Row::new();
        row2.insert("value".to_string(), Value::Integer(2));
        let multiple = QueryResult::with_rows(vec![row1, row2]);
        assert!(i64::from_single_row(multiple).is_err());
    }

    #[test]
    fn test_from_optional_row() {
        let empty = QueryResult::new();
        let result: Option<i64> = i64::from_optional_row(empty).unwrap();
        assert_eq!(result, None);

        let mut row = Row::new();
        row.insert("value".to_string(), Value::Integer(42));
        let single = QueryResult::with_rows(vec![row]);
        let result: Option<i64> = i64::from_optional_row(single).unwrap();
        assert_eq!(result, Some(42));
    }

    #[test]
    fn test_from_value_primitives() {
        assert_eq!(bool::from_value(&Value::Bool(true)).unwrap(), true);
        assert_eq!(i64::from_value(&Value::Integer(42)).unwrap(), 42);
        assert_eq!(
            String::from_value(&Value::String("hi".to_string())).unwrap(),
            "hi"
        );
    }

    #[test]
    fn test_from_value_option() {
        let null = Value::Null;
        let result: Option<i64> = Option::from_value(&null).unwrap();
        assert_eq!(result, None);

        let value = Value::Integer(42);
        let result: Option<i64> = Option::from_value(&value).unwrap();
        assert_eq!(result, Some(42));
    }

    #[test]
    fn test_from_value_vec() {
        let array = Value::Array(vec![
            Value::Integer(1),
            Value::Integer(2),
            Value::Integer(3),
        ]);
        let result: Vec<i64> = Vec::from_value(&array).unwrap();
        assert_eq!(result, vec![1, 2, 3]);
    }

    #[test]
    fn test_from_value_type_mismatch() {
        let value = Value::Integer(42);
        assert!(bool::from_value(&value).is_err());
        assert!(String::from_value(&value).is_err());
    }

    // Example of a custom type implementing FromRow
    #[derive(Debug, PartialEq)]
    struct TestUser {
        id: i64,
        name: String,
        active: bool,
    }

    impl FromRow for TestUser {
        fn from_row(row: Row) -> Result<Self> {
            Ok(TestUser {
                id: row.get_required("id")?.as_i64()?,
                name: row.get_required("name")?.as_str()?.to_string(),
                active: row.get_required("active")?.as_bool()?,
            })
        }
    }

    #[test]
    fn test_custom_from_row() {
        let mut row = Row::new();
        row.insert("id".to_string(), Value::Integer(1));
        row.insert("name".to_string(), Value::String("Alice".to_string()));
        row.insert("active".to_string(), Value::Bool(true));

        let user = TestUser::from_row(row).unwrap();
        assert_eq!(
            user,
            TestUser {
                id: 1,
                name: "Alice".to_string(),
                active: true,
            }
        );
    }

    #[test]
    fn test_custom_from_rows() {
        let mut row1 = Row::new();
        row1.insert("id".to_string(), Value::Integer(1));
        row1.insert("name".to_string(), Value::String("Alice".to_string()));
        row1.insert("active".to_string(), Value::Bool(true));

        let mut row2 = Row::new();
        row2.insert("id".to_string(), Value::Integer(2));
        row2.insert("name".to_string(), Value::String("Bob".to_string()));
        row2.insert("active".to_string(), Value::Bool(false));

        let result = QueryResult::with_rows(vec![row1, row2]);
        let users = TestUser::from_rows(result).unwrap();

        assert_eq!(users.len(), 2);
        assert_eq!(users[0].name, "Alice");
        assert_eq!(users[1].name, "Bob");
    }
}