1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! All the quaint-wrangling for the sqlite connector should happen here.

pub(crate) use quaint::connector::rusqlite;

use quaint::connector::{GetRow, ToColumnNames};
use schema_connector::{ConnectorError, ConnectorResult};
use sql_schema_describer::{sqlite as describer, DescriberErrorKind, SqlSchema};
use std::sync::Mutex;
use user_facing_errors::schema_engine::ApplyMigrationError;

pub(super) struct Connection(Mutex<rusqlite::Connection>);

impl Connection {
    pub(super) fn new(params: &super::Params) -> ConnectorResult<Self> {
        Ok(Connection(Mutex::new(
            rusqlite::Connection::open(&params.file_path).map_err(convert_error)?,
        )))
    }

    pub(super) fn new_in_memory() -> Self {
        Connection(Mutex::new(rusqlite::Connection::open_in_memory().unwrap()))
    }

    pub(super) async fn describe_schema(&mut self) -> ConnectorResult<SqlSchema> {
        describer::SqlSchemaDescriber::new(&self.0)
            .describe_impl()
            .await
            .map_err(|err| match err.into_kind() {
                DescriberErrorKind::QuaintError(err) => {
                    ConnectorError::from_source(err, "Error describing the database.")
                }
                DescriberErrorKind::CrossSchemaReference { .. } => {
                    unreachable!("No schemas on SQLite")
                }
            })
    }

    pub(super) fn raw_cmd(&mut self, sql: &str) -> ConnectorResult<()> {
        tracing::debug!(query_type = "raw_cmd", sql);
        let conn = self.0.lock().unwrap();
        conn.execute_batch(sql).map_err(convert_error)
    }

    pub(super) fn query(&mut self, query: quaint::ast::Query<'_>) -> ConnectorResult<quaint::prelude::ResultSet> {
        use quaint::visitor::Visitor;
        let (sql, params) = quaint::visitor::Sqlite::build(query).unwrap();
        self.query_raw(&sql, &params)
    }

    pub(super) fn query_raw(
        &mut self,
        sql: &str,
        params: &[quaint::prelude::Value<'_>],
    ) -> ConnectorResult<quaint::prelude::ResultSet> {
        tracing::debug!(query_type = "query_raw", sql);
        let conn = self.0.lock().unwrap();
        let mut stmt = conn.prepare_cached(sql).map_err(convert_error)?;

        let mut rows = stmt
            .query(rusqlite::params_from_iter(params.iter()))
            .map_err(convert_error)?;
        let column_names = rows.to_column_names();
        let mut converted_rows = Vec::new();
        while let Some(row) = rows.next().map_err(convert_error)? {
            converted_rows.push(row.get_result_row().unwrap());
        }

        Ok(quaint::prelude::ResultSet::new(column_names, converted_rows))
    }
}

pub(super) fn generic_apply_migration_script(
    migration_name: &str,
    script: &str,
    conn: &Connection,
) -> ConnectorResult<()> {
    tracing::debug!(query_type = "raw_cmd", sql = script);
    let conn = conn.0.lock().unwrap();
    conn.execute_batch(script).map_err(|sqlite_error: rusqlite::Error| {
        let database_error_code = match sqlite_error {
            rusqlite::Error::SqliteFailure(rusqlite::ffi::Error { extended_code, .. }, _)
            | rusqlite::Error::SqlInputError {
                error: rusqlite::ffi::Error { extended_code, .. },
                ..
            } => extended_code.to_string(),
            _ => "none".to_owned(),
        };

        ConnectorError::user_facing(ApplyMigrationError {
            migration_name: migration_name.to_owned(),
            database_error_code,
            database_error: sqlite_error.to_string(),
        })
    })
}

fn convert_error(err: rusqlite::Error) -> ConnectorError {
    ConnectorError::from_source(err, "SQLite database error")
}