#![allow(clippy::unwrap_used, clippy::expect_used)]
use mssql_client::{Client, Config, Error, Ready};
use std::time::Duration;
struct AzureRetryConfig {
max_retries: u32,
initial_backoff: Duration,
max_backoff: Duration,
}
impl Default for AzureRetryConfig {
fn default() -> Self {
Self {
max_retries: 5,
initial_backoff: Duration::from_millis(100),
max_backoff: Duration::from_secs(60),
}
}
}
async fn connect_with_retry(
config: Config,
retry_config: &AzureRetryConfig,
) -> Result<Client<Ready>, Error> {
let mut attempts = 0;
let mut last_error = None;
while attempts <= retry_config.max_retries {
if attempts > 0 {
let backoff = std::cmp::min(
retry_config.initial_backoff * 2u32.pow(attempts - 1),
retry_config.max_backoff,
);
let jitter = backoff / 5;
let total_wait = backoff + jitter;
println!(
" Retry {}/{} after {:?}",
attempts, retry_config.max_retries, total_wait
);
tokio::time::sleep(total_wait).await;
}
match Client::connect(config.clone()).await {
Ok(client) => return Ok(client),
Err(e) if e.is_transient() => {
println!(" Transient error: {e:?}");
last_error = Some(e);
attempts += 1;
}
Err(e) => return Err(e),
}
}
Err(last_error.unwrap_or_else(|| Error::Connection("Max retries exceeded".into())))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let server = std::env::var("AZURE_SQL_SERVER").ok();
let database = std::env::var("AZURE_SQL_DATABASE").ok();
let user = std::env::var("AZURE_SQL_USER").ok();
let password = std::env::var("AZURE_SQL_PASSWORD").ok();
println!("=== Azure SQL Database Connection Example ===\n");
if server.is_none() {
println!("Note: AZURE_SQL_* environment variables not set.");
println!("This example will demonstrate the connection pattern.\n");
demonstrate_azure_patterns();
return Ok(());
}
let server = server.unwrap();
let database = database.unwrap_or_else(|| "master".into());
let user = user.unwrap();
let password = password.unwrap();
let conn_str = format!(
"Server={server};Database={database};User Id={user};Password={password};\
Encrypt=true;TrustServerCertificate=false;Connection Timeout=30;\
Application Name=rust-mssql-driver-example"
);
let config = Config::from_connection_string(&conn_str)?;
println!("Connecting to Azure SQL: {server}");
println!(" Database: {database}");
println!(" Encryption: enabled (required for Azure)");
println!();
let retry_config = AzureRetryConfig::default();
let mut client = connect_with_retry(config, &retry_config).await?;
println!("Connected successfully!\n");
println!("1. Azure SQL Server Information:");
let rows = client
.query(
"SELECT @@VERSION, SERVERPROPERTY('Edition'), SERVERPROPERTY('EngineEdition')",
&[],
)
.await?;
for result in rows {
let row = result?;
let version: String = row.get(0)?;
let edition: String = row.get(1)?;
let engine: i32 = row.get(2)?;
println!(" Version: {}...", &version[..60.min(version.len())]);
println!(" Edition: {edition}");
println!(
" Engine: {} ({})",
engine,
match engine {
5 => "Azure SQL Database",
6 => "Azure SQL Data Warehouse",
8 => "Azure SQL Managed Instance",
_ => "Unknown",
}
);
}
println!("\n2. Service Tier Information:");
let rows = client
.query(
"SELECT \
DATABASEPROPERTYEX(DB_NAME(), 'ServiceObjective') AS ServiceTier, \
DATABASEPROPERTYEX(DB_NAME(), 'Edition') AS Edition",
&[],
)
.await?;
for result in rows {
let row = result?;
let tier: Option<String> = row.try_get(0);
let edition: Option<String> = row.try_get(1);
println!(" Service Tier: {tier:?}");
println!(" Edition: {edition:?}");
}
println!("\n3. Parameterized Query (RPC):");
let name = "test_user";
let rows = client
.query(
"SELECT @p1 AS input, SUSER_NAME() AS current_user",
&[&name],
)
.await?;
for result in rows {
let row = result?;
let input: String = row.get(0)?;
let current: String = row.get(1)?;
println!(" Input: {input}, Current User: {current}");
}
println!("\n4. Azure-specific considerations:");
println!(" - Redirects: Handled automatically by driver");
println!(" - Throttling: Use exponential backoff (implemented above)");
println!(" - Failover: Reconnect with retry on connection loss");
println!(" - Read replicas: Use ApplicationIntent=ReadOnly for read workloads");
client.close().await?;
println!("\nConnection closed.");
Ok(())
}
fn demonstrate_azure_patterns() {
println!("Azure SQL Connection Patterns:\n");
println!("1. Connection String Format:");
println!(" Server=yourserver.database.windows.net;");
println!(" Database=yourdb;");
println!(" User Id=youruser;");
println!(" Password=yourpassword;");
println!(" Encrypt=true;");
println!(" TrustServerCertificate=false;");
println!();
println!("2. Azure-Specific Transient Errors (retriable):");
let azure_errors = [
(40501, "Service is busy - retry with backoff"),
(40613, "Database unavailable - failover in progress"),
(49918, "Cannot process request - insufficient resources"),
(40197, "Service error - retry"),
];
for (code, desc) in azure_errors {
println!(" Error {code}: {desc}");
}
println!();
println!("3. Read Replica Usage:");
println!(" // Connection string for read-only workloads:");
println!(" Server=...;ApplicationIntent=ReadOnly;...");
println!();
println!("4. Retry Configuration for Azure:");
println!(" - Max retries: 5 (more than on-premises)");
println!(" - Initial backoff: 100ms");
println!(" - Max backoff: 60s");
println!(" - Always use jitter to avoid thundering herd");
println!("\n5. Error categorization (built-in):");
let test_errors = [
(40501, "Service busy"),
(40613, "Database unavailable"),
(102, "Syntax error"),
(2627, "Constraint violation"),
];
for (code, name) in test_errors {
let transient = Error::is_transient_server_error(code);
let terminal = Error::is_terminal_server_error(code);
println!(" Error {code}: {name} - transient: {transient}, terminal: {terminal}");
}
}