anansi-core 0.14.2

Anansi's core.
Documentation
use std::{str, path::PathBuf};
use tokio::process::Command;
use toml::Value::Table;
use async_trait::async_trait;
use sqlx::sqlite::Sqlite;

use crate::try_sqlx;
use crate::server::{Settings, MAX_CONNECTIONS};
use crate::records::Record;
use crate::web::{Result, BASE_DIR, WebErrorKind};
use crate::db::{DbRow, DbRowVec, DbPool, DbType, Builder, sql_stmt};

pub struct SqliteDbRow {
    row: sqlx::sqlite::SqliteRow,
}

impl DbRow for SqliteDbRow {
    type RawRow = sqlx::sqlite::SqliteRow;
    fn new(row: Self::RawRow) -> Self {
        Self {row}
    }
    fn try_bool(&self, index: &str) -> Result<bool> {
        try_sqlx!(self, index)
    }
    fn try_i32(&self, index: &str) -> Result<i32> {
        try_sqlx!(self, index)
    }
    fn try_i64(&self, index: &str) -> Result<i64> {
        try_sqlx!(self, index)
    }
    fn try_count(&self) -> Result<i64> {
        use sqlx::Row;
        match self.row.try_get(0) {
            Ok(o) => Ok(o),
            Err(e) => Err(Box::new(e))
        }
    }
    fn try_option_string(&self, index: &str) -> Result<Option<String>> {
        try_sqlx!(self, index)
    }
    fn try_string(&self, index: &str) -> Result<String> {
        try_sqlx!(self, index)
    }
    fn try_date_time(&self, index: &str) -> Result<String> {
        try_sqlx!(self, index)
    }
}

pub struct SqliteDbRowVec {
    rows: Vec<sqlx::sqlite::SqliteRow>,
}

impl DbRowVec for SqliteDbRowVec {
    type Row = SqliteDbRow;
    type Rows = Vec<sqlx::sqlite::SqliteRow>;
    fn from(rows: Self::Rows) -> Self {
        Self {rows}
    }
}

impl IntoIterator for SqliteDbRowVec {
    type Item = SqliteDbRow;
    type IntoIter = SqliteDbRowIntoIter;
    
    fn into_iter(self) -> Self::IntoIter {
        SqliteDbRowIntoIter {
            row_into_iter: self.rows.into_iter(),
        }
    }
}

pub struct SqliteDbRowIntoIter {
    row_into_iter: std::vec::IntoIter<sqlx::sqlite::SqliteRow>,
}

impl<'a> Iterator for SqliteDbRowIntoIter {
    type Item = SqliteDbRow;
    fn next(&mut self) -> Option<SqliteDbRow> {
        match self.row_into_iter.next() {
            Some(row) => Some(SqliteDbRow {row}),
            None => None,
        }
    }
}

#[derive(Clone, Debug)]
pub struct SqliteDbPool(pub(in crate) sqlx::Pool<Sqlite>);

#[async_trait]
impl DbPool for SqliteDbPool {
    type SqlRow = SqliteDbRow;
    type SqlRowVec = SqliteDbRowVec;
    async fn new(settings: &Settings) -> Result<Self> {
        let mut dir = PathBuf::new();
        BASE_DIR.with(|base| dir = base.clone());
        let databases = match settings.get("databases") {
            Some(v) => match v {
                Table(t) => t,
                _ => return Err(WebErrorKind::BadDb.to_box()),
            }
            None => return Err(WebErrorKind::BadDb.to_box()),
        };
        let name = match databases.get("default") {
            Some(d) => d.get("name").expect("Could not get database name").as_str().expect("Could not convert database name to string"),
            None => return Err(WebErrorKind::BadDb.to_box()),
        };
        dir.push(name);
        let ds = dir.to_str().unwrap();
        match Self::connect(ds).await {
            Ok(p) => Ok(Self {0: p}),
            Err(e) => {
                Self::init_db(ds).await;
                Err(e)
            }
        }
    }
    async fn query(&self, val: &str) -> Result<SqliteDbRowVec> {
        Ok(SqliteDbRowVec {rows: sqlx::query(val).fetch_all(&self.0).await?})
    }
    async fn raw_fetch_one(&self, val: &str) -> Result<Self::SqlRow> {
        Ok(Self::SqlRow {row: sqlx::query(val).fetch_one(&self.0).await?})
    }
    async fn raw_fetch_all(&self, val: &str) -> Result<Self::SqlRowVec> {
        Ok(Self::SqlRowVec {rows: sqlx::query(val).fetch_all(&self.0).await?})
    }
    async fn raw_execute(&self, val: &str) -> Result<()> {
        match sqlx::query(val).execute(&self.0).await {
            Ok(_) => Ok(()),
            Err(e) => Err(Box::new(e)),
        }
    }
    fn now() -> &'static str {
        "strftime('%Y-%m-%d %H-%M-%f','now')"
    }
    async fn test() -> Result<Self> {
        let pool = sqlx::sqlite::SqlitePoolOptions::new()
            .max_connections(MAX_CONNECTIONS as u32)
            .connect(":memory:").await?;
        sqlx::query(INIT_STR).execute(&pool).await?;
        let pool = Self {0: pool};
        Ok(pool)
    }
    fn to_stmt<R: Record>(val: Builder<R>) -> String {
        sql_stmt(val)
    }
}

impl DbType for SqliteDbPool {
    fn db_type(ty: &str) -> String {
        ty.to_string()
    }
}

static INIT_STR: &str = "CREATE TABLE \"anansi_records\"(\n\t\"name\" text NOT NULL,\n\t\"schema\" text NOT NULL\n);\nCREATE TABLE anansi_migrations(\n\t\"id\" INT PRIMARY KEY,\n\t\"app\" TEXT NOT NULL,\n\t\"name\" TEXT NOT NULL,\n\t\"applied\" DATETIME NOT NULL\n);\n";

impl SqliteDbPool {
    async fn connect(dir: &str) -> Result<sqlx::Pool<Sqlite>> {
        sqlx::sqlite::SqlitePoolOptions::new()
            .max_connections(MAX_CONNECTIONS as u32)
            .connect(&dir).await.or(Err(WebErrorKind::BadDb.to_box()))
    }

    async fn init_db(dir: &str) {
        let mut cmd = Command::new("sqlite3");
        cmd.arg(dir);
        cmd.arg(INIT_STR);
        let mut child = cmd.spawn().expect("Failed to start sqlite3");
        child.wait().await.expect("failed to wait on child");
        println!("Initialized database");
    }
}