burncloud-database-client 0.1.1

High-level database client with migrations, pooling, and AI model management for BurnCloud
Documentation
use burncloud_database_core::{
    Repository, QueryContext, QueryOptions, QueryParam, QueryExecutor
};
use burncloud_database_core::error::DatabaseResult;
use async_trait::async_trait;
use serde::{Serialize, Deserialize};
use std::sync::Arc;
use std::marker::PhantomData;

pub struct BaseRepository<T>
where
    T: Send + Sync + Serialize + for<'de> Deserialize<'de>,
{
    pub query_executor: Arc<Box<dyn QueryExecutor>>,
    table_name: String,
    _phantom: PhantomData<T>,
}

impl<T> BaseRepository<T>
where
    T: Send + Sync + Serialize + for<'de> Deserialize<'de>,
{
    pub fn new(query_executor: Box<dyn QueryExecutor>, table_name: String) -> Self {
        Self {
            query_executor: Arc::new(query_executor),
            table_name,
            _phantom: PhantomData,
        }
    }

    pub fn get_table_name(&self) -> &str {
        &self.table_name
    }
}

#[async_trait]
impl<T> Repository<T> for BaseRepository<T>
where
    T: Send + Sync + Serialize + for<'de> Deserialize<'de>,
{
    async fn find_by_id(&self, id: &str, context: &QueryContext) -> DatabaseResult<Option<T>> {
        let query = format!("SELECT * FROM {} WHERE id = $1", self.get_table_name());
        let id_param = burncloud_database_impl::StringParam(id.to_string());
        let params: Vec<&dyn QueryParam> = vec![&id_param];

        let result = self.query_executor.execute_query(&query, &params, context).await?;

        if result.rows.is_empty() {
            Ok(None)
        } else {
            let row = &result.rows[0];
            let entity: T = serde_json::from_value(serde_json::Value::Object(
                row.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
            )).map_err(|e| burncloud_database_core::DatabaseError::SerializationError(e.to_string()))?;
            Ok(Some(entity))
        }
    }

    async fn find_all(&self, options: &QueryOptions, context: &QueryContext) -> DatabaseResult<Vec<T>> {
        let mut query = format!("SELECT * FROM {}", self.get_table_name());

        if let Some(order_by) = &options.order_by {
            query.push_str(&format!(" ORDER BY {}", order_by));
            if let Some(direction) = &options.order_direction {
                match direction {
                    burncloud_database_core::OrderDirection::Asc => query.push_str(" ASC"),
                    burncloud_database_core::OrderDirection::Desc => query.push_str(" DESC"),
                }
            }
        }

        if let Some(limit) = options.limit {
            query.push_str(&format!(" LIMIT {}", limit));
        }

        if let Some(offset) = options.offset {
            query.push_str(&format!(" OFFSET {}", offset));
        }

        let params: Vec<&dyn QueryParam> = vec![];
        let result = self.query_executor.execute_query(&query, &params, context).await?;

        let mut entities = Vec::new();
        for row in result.rows {
            let entity: T = serde_json::from_value(serde_json::Value::Object(
                row.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
            )).map_err(|e| burncloud_database_core::DatabaseError::SerializationError(e.to_string()))?;
            entities.push(entity);
        }

        Ok(entities)
    }

    async fn create(&self, entity: &T, context: &QueryContext) -> DatabaseResult<String> {
        let entity_json = serde_json::to_value(entity)
            .map_err(|e| burncloud_database_core::DatabaseError::SerializationError(e.to_string()))?;

        if let serde_json::Value::Object(map) = entity_json {
            let columns: Vec<String> = map.keys().cloned().collect();
            let placeholders: Vec<String> = (1..=columns.len()).map(|i| format!("${}", i)).collect();

            let query = format!(
                "INSERT INTO {} ({}) VALUES ({}) RETURNING id",
                self.get_table_name(),
                columns.join(", "),
                placeholders.join(", ")
            );

            let params: Vec<&dyn QueryParam> = vec![];
            let result = self.query_executor.execute_query(&query, &params, context).await?;

            Ok(result.last_insert_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()))
        } else {
            Err(burncloud_database_core::DatabaseError::SerializationError(
                "Entity must be a JSON object".to_string()
            ))
        }
    }

    async fn update(&self, _id: &str, entity: &T, context: &QueryContext) -> DatabaseResult<()> {
        let entity_json = serde_json::to_value(entity)
            .map_err(|e| burncloud_database_core::DatabaseError::SerializationError(e.to_string()))?;

        if let serde_json::Value::Object(map) = entity_json {
            let set_clauses: Vec<String> = map.keys().enumerate()
                .map(|(i, key)| format!("{} = ${}", key, i + 1))
                .collect();

            let query = format!(
                "UPDATE {} SET {} WHERE id = ${}",
                self.get_table_name(),
                set_clauses.join(", "),
                map.len() + 1
            );

            let params: Vec<&dyn QueryParam> = vec![];
            self.query_executor.execute_query(&query, &params, context).await?;

            Ok(())
        } else {
            Err(burncloud_database_core::DatabaseError::SerializationError(
                "Entity must be a JSON object".to_string()
            ))
        }
    }

    async fn delete(&self, id: &str, context: &QueryContext) -> DatabaseResult<()> {
        let query = format!("DELETE FROM {} WHERE id = $1", self.get_table_name());
        let id_param = burncloud_database_impl::StringParam(id.to_string());
        let params: Vec<&dyn QueryParam> = vec![&id_param];

        self.query_executor.execute_query(&query, &params, context).await?;
        Ok(())
    }

    async fn exists(&self, id: &str, context: &QueryContext) -> DatabaseResult<bool> {
        let query = format!("SELECT 1 FROM {} WHERE id = $1 LIMIT 1", self.get_table_name());
        let id_param = burncloud_database_impl::StringParam(id.to_string());
        let params: Vec<&dyn QueryParam> = vec![&id_param];

        let result = self.query_executor.execute_query(&query, &params, context).await?;
        Ok(!result.rows.is_empty())
    }
}