sqlx-sqlserver 0.0.1-alpha

Independent Microsoft SQL Server driver crate for SQLx.
Documentation
use sqlx_sqlserver::MssqlConnectOptions;
#[cfg(feature = "integration-tests")]
use std::sync::Once;

#[cfg(feature = "integration-tests")]
use sqlx_core::column::Column;
#[cfg(feature = "integration-tests")]
use sqlx_core::connection::{ConnectOptions, Connection};
#[cfg(feature = "integration-tests")]
use sqlx_core::executor::Executor;
#[cfg(feature = "integration-tests")]
use sqlx_core::row::Row;
#[cfg(all(feature = "integration-tests", feature = "migrate"))]
use sqlx_core::sql_str::SqlSafeStr;
#[cfg(feature = "integration-tests")]
use sqlx_core::statement::Statement;
#[cfg(feature = "integration-tests")]
use sqlx_core::value::ValueRef;
#[cfg(all(feature = "integration-tests", feature = "migrate"))]
use std::borrow::Cow;

fn database_url() -> Option<String> {
    std::env::var("MSSQL_DATABASE_URL")
        .ok()
        .and_then(|url| match url.trim() {
            "" => None,
            url => Some(url.to_owned()),
        })
}

#[cfg(feature = "integration-tests")]
async fn any_test_conn(
    test_name: &str,
) -> Result<Option<sqlx_core::any::AnyConnection>, Box<dyn std::error::Error>> {
    static INSTALL: Once = Once::new();

    let Some(url) = database_url() else {
        eprintln!("skipping {test_name}: MSSQL_DATABASE_URL is not set");
        return Ok(None);
    };

    INSTALL.call_once(|| {
        sqlx_core::any::driver::install_drivers(&[sqlx_sqlserver::any::DRIVER])
            .expect("SQL Server Any driver should install once");
    });

    Ok(Some(sqlx_core::any::AnyConnection::connect(&url).await?))
}

#[cfg(all(feature = "integration-tests", feature = "migrate"))]
async fn any_migrate_test_conn(
    test_name: &str,
) -> Result<Option<sqlx_core::any::AnyConnection>, Box<dyn std::error::Error>> {
    any_test_conn(test_name).await
}

#[cfg(feature = "integration-tests")]
async fn native_test_conn(
    test_name: &str,
) -> Result<Option<sqlx_sqlserver::MssqlConnection>, Box<dyn std::error::Error>> {
    let Some(url) = database_url() else {
        eprintln!("skipping {test_name}: MSSQL_DATABASE_URL is not set");
        return Ok(None);
    };

    let options = url.parse::<MssqlConnectOptions>()?;
    Ok(Some(options.connect().await?))
}

#[test]
fn mssql_database_url_is_parseable_when_set() {
    let Some(url) = database_url() else {
        eprintln!("skipping SQL Server smoke test: MSSQL_DATABASE_URL is not set");
        return;
    };

    url.parse::<MssqlConnectOptions>()
        .expect("MSSQL_DATABASE_URL should parse as SQL Server options");
}

#[cfg(feature = "migrate")]
#[test]
fn any_driver_exposes_migrate_database_when_feature_enabled() {
    sqlx_sqlserver::any::DRIVER
        .get_migrate_database()
        .expect("SQL Server Any driver should expose migration database hooks");
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn connects_and_pings_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = native_test_conn("SQL Server integration test").await? else {
        return Ok(());
    };

    conn.ping().await?;
    conn.close().await?;

    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn fetches_select_one_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = native_test_conn("SQL Server query test").await? else {
        return Ok(());
    };

    let row = sqlx_core::query::query("SELECT 1")
        .fetch_one(&mut conn)
        .await?;

    assert_eq!(1_i32, row.try_get::<i32, _>(0)?);

    conn.close().await?;
    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn fetches_bound_scalars_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = native_test_conn("SQL Server bound scalar test").await? else {
        return Ok(());
    };

    let row = sqlx_core::query::query("SELECT @p1, @p2")
        .bind(7_i32)
        .bind("hello")
        .fetch_one(&mut conn)
        .await?;

    assert_eq!(7_i32, row.try_get::<i32, _>(0)?);
    assert_eq!("hello", row.try_get::<String, _>(1)?);

    conn.close().await?;
    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn fetches_bound_null_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = native_test_conn("SQL Server bound null test").await? else {
        return Ok(());
    };

    let row = sqlx_core::query::query("SELECT @p1")
        .bind(Option::<i32>::None)
        .fetch_one(&mut conn)
        .await?;

    assert!(row.try_get_raw(0)?.is_null());

    conn.close().await?;
    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn prepares_statement_metadata_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = native_test_conn("SQL Server prepare test").await? else {
        return Ok(());
    };

    let statement = conn
        .prepare(sqlx_core::sql_str::SqlStr::from_static("SELECT 1 AS value"))
        .await?;

    assert_eq!(1, statement.columns().len());
    assert_eq!("value", statement.columns()[0].name());

    conn.close().await?;
    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn rolls_back_transaction_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = native_test_conn("SQL Server transaction test").await? else {
        return Ok(());
    };

    let tx = conn.begin().await?;
    tx.rollback().await?;

    conn.close().await?;
    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn any_fetches_bound_integer_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = any_test_conn("SQL Server Any bound argument test").await? else {
        return Ok(());
    };

    let row = sqlx_core::query::query("SELECT @p1")
        .bind(7_i32)
        .fetch_one(&mut conn)
        .await?;

    assert_eq!(7_i32, row.try_get::<i32, _>(0)?);

    conn.close().await?;
    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn any_prepares_statement_metadata_when_configured() -> Result<(), Box<dyn std::error::Error>>
{
    let Some(mut conn) = any_test_conn("SQL Server Any prepare test").await? else {
        return Ok(());
    };

    let statement = conn
        .prepare(sqlx_core::sql_str::SqlStr::from_static("SELECT 1 AS value"))
        .await?;

    assert_eq!(1, statement.columns().len());
    assert_eq!("value", statement.columns()[0].name());

    conn.close().await?;
    Ok(())
}

#[cfg(feature = "integration-tests")]
#[tokio::test]
async fn any_fetches_select_one_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = any_test_conn("SQL Server Any query test").await? else {
        return Ok(());
    };

    let row = sqlx_core::query::query("SELECT 1")
        .fetch_one(&mut conn)
        .await?;

    assert_eq!(1_i32, row.try_get::<i32, _>(0)?);

    conn.close().await?;
    Ok(())
}

#[cfg(all(feature = "integration-tests", feature = "migrate"))]
#[tokio::test]
async fn runs_migration_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = native_test_conn("SQL Server migration test").await? else {
        return Ok(());
    };

    let table_name = "_sqlx_migrations_smoke";
    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}_table"
        )))
        .await?;
    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}"
        )))
        .await?;

    let migration = sqlx_core::migrate::Migration::new(
        1,
        Cow::Borrowed("create smoke table"),
        sqlx_core::migrate::MigrationType::Simple,
        sqlx_core::sql_str::AssertSqlSafe(format!(
            "CREATE TABLE {table_name}_table (id INT NOT NULL)"
        ))
        .into_sql_str(),
        false,
    );
    let mut migrator = sqlx_core::migrate::Migrator::with_migrations(vec![migration]);
    migrator.dangerous_set_table_name(table_name.to_owned());

    migrator.run_direct(None, &mut conn, false).await?;

    let exists: bool = sqlx_core::query_scalar::query_scalar(
        "SELECT CONVERT(bit, CASE WHEN OBJECT_ID(N'_sqlx_migrations_smoke_table', N'U') IS NULL THEN 0 ELSE 1 END)",
    )
    .fetch_one(&mut conn)
    .await?;

    assert!(exists);

    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}_table"
        )))
        .await?;
    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}"
        )))
        .await?;
    conn.close().await?;

    Ok(())
}

#[cfg(all(feature = "integration-tests", feature = "migrate"))]
#[tokio::test]
async fn any_runs_migration_when_configured() -> Result<(), Box<dyn std::error::Error>> {
    let Some(mut conn) = any_migrate_test_conn("SQL Server Any migration test").await? else {
        return Ok(());
    };

    let table_name = "_sqlx_any_migrations_smoke";
    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}_table"
        )))
        .await?;
    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}"
        )))
        .await?;

    let migration = sqlx_core::migrate::Migration::new(
        1,
        Cow::Borrowed("create any smoke table"),
        sqlx_core::migrate::MigrationType::Simple,
        sqlx_core::sql_str::AssertSqlSafe(format!(
            "CREATE TABLE {table_name}_table (id INT NOT NULL)"
        ))
        .into_sql_str(),
        false,
    );
    let mut migrator = sqlx_core::migrate::Migrator::with_migrations(vec![migration]);
    migrator.dangerous_set_table_name(table_name.to_owned());

    migrator.run_direct(None, &mut conn, false).await?;

    let exists: bool = sqlx_core::query_scalar::query_scalar(
        "SELECT CONVERT(bit, CASE WHEN OBJECT_ID(N'_sqlx_any_migrations_smoke_table', N'U') IS NULL THEN 0 ELSE 1 END)",
    )
    .fetch_one(&mut conn)
    .await?;

    assert!(exists);

    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}_table"
        )))
        .await?;
    let _ = conn
        .execute(sqlx_core::sql_str::AssertSqlSafe(format!(
            "DROP TABLE IF EXISTS {table_name}"
        )))
        .await?;
    conn.close().await?;

    Ok(())
}