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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! All the quaint-wrangling for the mssql connector should happen here.

use quaint::{
    connector::{self, MssqlUrl},
    prelude::{ConnectionInfo, NativeConnectionInfo, Queryable},
};
use schema_connector::{ConnectorError, ConnectorResult, Namespaces};
use sql_schema_describer::{mssql as describer, DescriberErrorKind, SqlSchema, SqlSchemaDescriberBackend};
use user_facing_errors::{schema_engine::ApplyMigrationError, schema_engine::DatabaseSchemaInconsistent, KnownError};

pub(super) struct Connection(connector::Mssql);

impl Connection {
    pub(super) async fn new(connection_str: &str) -> ConnectorResult<Connection> {
        let url = MssqlUrl::new(connection_str).map_err(|err| {
            ConnectorError::user_facing(user_facing_errors::common::InvalidConnectionString {
                details: err.to_string(),
            })
        })?;
        Ok(Connection(
            connector::Mssql::new(url.clone()).await.map_err(quaint_err_url(&url))?,
        ))
    }

    #[tracing::instrument(skip(self, params))]
    pub(super) async fn describe_schema(
        &mut self,
        params: &super::Params,
        namespaces: Option<Namespaces>,
    ) -> ConnectorResult<SqlSchema> {
        let namespaces_vec = Namespaces::to_vec(namespaces, String::from(params.url.schema()));
        let namespaces_str: Vec<&str> = namespaces_vec.iter().map(AsRef::as_ref).collect();

        let mut schema = describer::SqlSchemaDescriber::new(&self.0)
            .describe(namespaces_str.as_slice())
            .await
            .map_err(|err| match err.into_kind() {
                DescriberErrorKind::QuaintError(err) => quaint_err_url(&params.url)(err),
                e @ DescriberErrorKind::CrossSchemaReference { .. } => {
                    let err = KnownError::new(DatabaseSchemaInconsistent {
                        explanation: e.to_string(),
                    });

                    ConnectorError::from(err)
                }
            })?;

        crate::flavour::normalize_sql_schema(&mut schema, params.connector_params.preview_features);

        Ok(schema)
    }

    pub(super) async fn raw_cmd(&mut self, sql: &str, params: &super::Params) -> ConnectorResult<()> {
        tracing::debug!(query_type = "raw_cmd", sql);
        self.0.raw_cmd(sql).await.map_err(quaint_err(params))
    }

    pub(super) async fn version(&mut self, params: &super::Params) -> ConnectorResult<Option<String>> {
        tracing::debug!(query_type = "version");
        self.0.version().await.map_err(quaint_err(params))
    }

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

    pub(super) async fn query_raw(
        &self,
        sql: &str,
        params: &[quaint::prelude::Value<'_>],
        conn_params: &super::Params,
    ) -> ConnectorResult<quaint::prelude::ResultSet> {
        tracing::debug!(query_type = "query_raw", sql, ?params);
        self.0.query_raw(sql, params).await.map_err(quaint_err(conn_params))
    }
}

pub(super) async fn generic_apply_migration_script(
    migration_name: &str,
    script: &str,
    conn: &mut Connection,
) -> ConnectorResult<()> {
    tracing::debug!(query_type = "raw_cmd", sql = script);
    conn.0.raw_cmd(script).await.map_err(|sql_error| {
        ConnectorError::user_facing(ApplyMigrationError {
            migration_name: migration_name.to_owned(),
            database_error_code: String::from(sql_error.original_code().unwrap_or("none")),
            database_error: sql_error
                .original_message()
                .map(String::from)
                .unwrap_or_else(|| sql_error.to_string()),
        })
    })
}

fn quaint_err(params: &super::Params) -> impl (Fn(quaint::error::Error) -> ConnectorError) + '_ {
    quaint_err_url(&params.url)
}

fn quaint_err_url(url: &MssqlUrl) -> impl (Fn(quaint::error::Error) -> ConnectorError) + '_ {
    |err| {
        crate::flavour::quaint_error_to_connector_error(
            err,
            &ConnectionInfo::Native(NativeConnectionInfo::Mssql(url.clone())),
        )
    }
}