rsql_driver_sqlserver 0.19.3

rsql sqlserver driver
Documentation
#[cfg(target_os = "linux")]
use indoc::indoc;
#[cfg(target_os = "linux")]
use jiff::civil;
#[cfg(target_os = "linux")]
use rsql_driver::{Connection, Driver, Value};
#[cfg(target_os = "linux")]
use std::str::FromStr;
#[cfg(target_os = "linux")]
use testcontainers::ContainerRequest;
#[cfg(target_os = "linux")]
use testcontainers::runners::AsyncRunner;
#[cfg(target_os = "linux")]
use testcontainers_modules::mssql_server::MssqlServer;

#[cfg(target_os = "linux")]
const PASSWORD: &str = "Password42!";

#[cfg(target_os = "linux")]
#[tokio::test]
async fn test_sqlserver_driver() -> anyhow::Result<()> {
    let image = ContainerRequest::from(
        MssqlServer::default()
            .with_accept_eula()
            .with_sa_password(PASSWORD),
    );
    let container = image.start().await?;
    let port = container.get_host_port_ipv4(1433).await?;
    let database_url =
        format!("sqlserver://sa:{PASSWORD}@127.0.0.1:{port}?TrustServerCertificate=true");
    let mut connection = rsql_driver_sqlserver::Driver
        .connect(database_url.as_str())
        .await?;
    assert_eq!(database_url, connection.url().as_str());

    test_connection_interface(&mut *connection).await?;
    test_data_types(&mut *connection).await?;

    container.stop().await?;
    container.rm().await?;
    Ok(())
}

#[cfg(target_os = "linux")]
async fn test_connection_interface(connection: &mut dyn Connection) -> anyhow::Result<()> {
    let _ = connection
        .execute("CREATE TABLE person (id INT, name VARCHAR(20))", &[])
        .await?;

    let rows = connection
        .execute("INSERT INTO person (id, name) VALUES (1, 'foo')", &[])
        .await?;
    assert_eq!(rows, 1);

    let mut query_result = connection.query("SELECT id, name FROM person", &[]).await?;
    assert_eq!(query_result.columns(), vec!["id", "name"]);
    assert_eq!(
        query_result.next().await.cloned(),
        Some(vec![Value::I32(1), Value::String("foo".to_string())])
    );
    assert!(query_result.next().await.is_none());

    let metadata = connection.metadata().await?;
    let catalog = metadata.current_catalog().expect("catalog");
    let schema = catalog.current_schema().expect("schema");
    assert!(schema.tables().iter().any(|table| table.name() == "person"));

    Ok(())
}

#[cfg(target_os = "linux")]
async fn test_data_types(connection: &mut dyn Connection) -> anyhow::Result<()> {
    let sql = indoc! {r"
        CREATE TABLE data_types (
            nchar_type NCHAR(1),
            char_type CHAR(1),
            nvarchar_type NVARCHAR(50),
            varchar_type VARCHAR(50),
            ntext_type NTEXT,
            text_type TEXT,
            binary_type BINARY(1),
            varbinary_type VARBINARY(1),
            tinyint_type TINYINT,
            smallint_type SMALLINT,
            int_type INT,
            bigint_type BIGINT,
            float24_type FLOAT(24),
            float53_type FLOAT(53),
            bit_type BIT,
            decimal_type DECIMAL(5,2),
            date_type DATE,
            time_type TIME,
            datetime_type DATETIME
        )
    "};
    let _ = connection.execute(sql, &[]).await?;

    let sql = indoc! {r"
        INSERT INTO data_types (
            nchar_type, char_type, nvarchar_type, varchar_type, ntext_type, text_type,
            binary_type, varbinary_type,
            tinyint_type, smallint_type, int_type, bigint_type,
            float24_type, float53_type, bit_type, decimal_type,
            date_type, time_type, datetime_type
        ) VALUES (
             'a', 'a', 'foo', 'foo', 'foo', 'foo',
             CAST(42 AS BINARY(1)), CAST(42 AS VARBINARY(1)),
             127, 32767, 2147483647, 9223372036854775807,
             123.45, 123.0, 1, 123.0,
             '2022-01-01', '14:30:00', '2022-01-01 14:30:00'
         )
    "};
    let _ = connection.execute(sql, &[]).await?;

    let sql = indoc! {r"
        SELECT nchar_type, char_type, nvarchar_type, varchar_type, ntext_type, text_type,
               binary_type, varbinary_type,
               tinyint_type, smallint_type, int_type, bigint_type,
               float24_type, float53_type, bit_type, decimal_type,
               date_type, time_type, datetime_type
          FROM data_types
    "};
    let mut query_result = connection.query(sql, &[]).await?;
    assert_eq!(
        query_result.next().await.cloned(),
        Some(vec![
            Value::String("a".to_string()),
            Value::String("a".to_string()),
            Value::String("foo".to_string()),
            Value::String("foo".to_string()),
            Value::String("foo".to_string()),
            Value::String("foo".to_string()),
            Value::Bytes(vec![42u8]),
            Value::Bytes(vec![42u8]),
            Value::U8(127),
            Value::I16(32_767),
            Value::I32(2_147_483_647),
            Value::I64(9_223_372_036_854_775_807),
            Value::F32(123.45),
            Value::F64(123.0),
            Value::Bool(true),
            Value::Decimal(rust_decimal::Decimal::from_str("123.00").expect("invalid decimal")),
            Value::Date(civil::date(2022, 1, 1)),
            Value::Time(civil::time(14, 30, 0, 0)),
            Value::DateTime(civil::datetime(2022, 1, 1, 14, 30, 0, 0))
        ])
    );
    assert!(query_result.next().await.is_none());

    Ok(())
}

#[cfg(target_os = "linux")]
#[tokio::test]
async fn test_sqlserver_metadata() -> anyhow::Result<()> {
    let image = ContainerRequest::from(
        MssqlServer::default()
            .with_accept_eula()
            .with_sa_password(PASSWORD),
    );
    let container = image.start().await?;
    let port = container.get_host_port_ipv4(1433).await?;
    let database_url =
        format!("sqlserver://sa:{PASSWORD}@127.0.0.1:{port}?TrustServerCertificate=true");
    let mut connection = rsql_driver_sqlserver::Driver
        .connect(database_url.as_str())
        .await?;

    test_schema(&mut *connection).await?;

    container.stop().await?;
    container.rm().await?;
    Ok(())
}

#[cfg(target_os = "linux")]
async fn test_schema(connection: &mut dyn Connection) -> anyhow::Result<()> {
    let _ = connection
        .execute(
            "CREATE TABLE contacts (id INT PRIMARY KEY, email VARCHAR(20))",
            &[],
        )
        .await?;
    let _ = connection
        .execute(
            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(20))",
            &[],
        )
        .await?;

    let metadata = connection.metadata().await?;
    let catalog = metadata.current_catalog().expect("catalog");
    let schema = catalog.current_schema().expect("schema");

    let contacts_table = schema.get("contacts").expect("contacts table");
    assert_eq!(contacts_table.name(), "contacts");
    assert_eq!(contacts_table.columns().len(), 2);
    let id_column = contacts_table.get_column("id").expect("id column");
    assert_eq!(id_column.name(), "id");
    assert_eq!(id_column.data_type(), "int");
    assert!(id_column.not_null());
    assert_eq!(id_column.default(), None);
    let email_column = contacts_table.get_column("email").expect("email column");
    assert_eq!(email_column.name(), "email");
    assert_eq!(email_column.data_type(), "varchar(20)");
    assert!(!email_column.not_null());
    assert_eq!(email_column.default(), None);

    let contacts_indexes = contacts_table
        .indexes()
        .iter()
        .map(|index| index.name())
        .collect::<Vec<_>>();
    assert!(contacts_indexes[0].contains(&"PK__contacts__".to_string()));

    let users_table = schema.get("users").expect("users table");
    assert_eq!(users_table.name(), "users");
    assert_eq!(users_table.columns().len(), 2);
    let id_column = users_table.get_column("id").expect("id column");
    assert_eq!(id_column.name(), "id");
    assert_eq!(id_column.data_type(), "int");
    assert!(id_column.not_null());
    assert_eq!(id_column.default(), None);
    let email_column = users_table.get_column("email").expect("email column");
    assert_eq!(email_column.name(), "email");
    assert_eq!(email_column.data_type(), "varchar(20)");
    assert!(!email_column.not_null());
    assert_eq!(email_column.default(), None);

    let user_indexes = users_table
        .indexes()
        .iter()
        .map(|index| index.name())
        .collect::<Vec<_>>();
    assert!(user_indexes[0].contains(&"PK__users__".to_string()));

    Ok(())
}