wasm-sql 0.1.6

Wasmtime host implementation for a SQL component WIT interface. Enables Wasm components to interact with SQL databases via the WebAssembly Component Model.
Documentation
use wasmtime::component::Accessor;

use crate::{
    core::bindings::{
        SqlHostState,
        generated::wasm_sql::core::{
            query::QueryExecutor,
            query_types::{SqlQuery, SqlString},
            util_types::Error,
        },
    },
    sqldb::SqlDatabase,
};

#[allow(dead_code)]
pub enum QueryOrRaw {
    Query(SqlQuery),
    Raw(SqlString),
}

#[macro_export]
macro_rules! execute_with {
    ($executor:expr, $accessor:expr, $query:expr, $op:ident) => {{
        use crate::core::bindings::generated::wasm_sql::core::util_types::Error;
        use sqlx::Executor;

        match $query {
            QueryOrRaw::Query(query) => {
                let query = {
                    let persistent = query.persistent.unwrap_or(true);

                    if let Some(args_resource) = query.args {
                        let args = $accessor.with(|mut access| {
                            let st = access.get();
                            st.table.delete(args_resource).map(|x| x.args.into_inner())
                        })?;
                        sqlx::query_with(&query.sql, args).persistent(persistent)
                    } else {
                        sqlx::query(&query.sql).persistent(persistent)
                    }

                };

                let result = $executor.$op(query).await?;
                Result::<_, Error>::Ok(result)
            }
            QueryOrRaw::Raw(sql) => {
                let query = sqlx::raw_sql(&sql);
                let result = $executor.$op(query).await?;
                Result::<_, Error>::Ok(result)
            }
        }
    }};
}

#[allow(dead_code)]
pub(crate) trait ErasedExecutor<D: wasmtime::component::HasData> {
    async fn fetch_all<T>(
        &self,
        query: QueryOrRaw,
        accessor: &Accessor<T, D>,
    ) -> Result<Vec<<SqlDatabase as sqlx::Database>::Row>, Error>;

    async fn execute<T>(
        &self,
        query: QueryOrRaw,
        accessor: &Accessor<T, D>,
    ) -> Result<<SqlDatabase as sqlx::Database>::QueryResult, Error>;
}

impl ErasedExecutor<SqlHostState> for QueryExecutor {
    async fn fetch_all<T>(
        &self,
        query: QueryOrRaw,
        accessor: &Accessor<T, SqlHostState>,
    ) -> Result<Vec<<SqlDatabase as sqlx::Database>::Row>, Error> {
        match self {
            QueryExecutor::Pool => {
                let pool = accessor.with(|mut access| access.get().sql_db.pool.clone());
                let rows = execute_with!(pool, accessor, query, fetch_all)?;

                Ok(rows)
            }
            QueryExecutor::Connection(resource) => {
                let conn_impl = accessor.with(|mut access| {
                    let state = access.get();
                    state.table.get(&resource).map(|conn| conn.clone())
                })?;

                conn_impl.fetch_all(query, accessor).await
            }
            QueryExecutor::Transaction(resource) => {
                let tx_impl = accessor.with(|mut access| {
                    let state = access.get();
                    state.table.get(&resource).map(|tx| tx.clone())
                })?;

                tx_impl.fetch_all(query, accessor).await
            }
        }
    }

    async fn execute<T>(
        &self,
        query: QueryOrRaw,
        accessor: &Accessor<T, SqlHostState>,
    ) -> Result<<SqlDatabase as sqlx::Database>::QueryResult, Error> {
        match self {
            QueryExecutor::Pool => {
                let pool = accessor.with(|mut access| access.get().sql_db.pool.clone());
                let rows = execute_with!(pool, accessor, query, execute)?;
                Ok(rows)
            }
            QueryExecutor::Connection(resource) => {
                let conn_impl = accessor.with(|mut access| {
                    let state = access.get();
                    state.table.get(&resource).map(|conn| conn.clone())
                })?;
                
                conn_impl.execute(query, accessor).await
            }
            QueryExecutor::Transaction(resource) => {
                let tx_impl = accessor.with(|mut access| {
                    let state = access.get();
                    state.table.get(&resource).map(|tx| tx.clone())
                })?;

                tx_impl.execute(query, accessor).await
            }
        }
    }
}