1use super::auth::{self, Encryption, Negotiated, TdsStream, TlsOptions};
4use super::protocol::{
5 self, DEFAULT_PACKET_SIZE, EnvChange, HEADER_LEN, Login7, PacketHeader, ServerError,
6 Token, TokenStream, packet,
7};
8use crate::config::DatabaseConfig;
9use crate::dialect::Dialect;
10use crate::driver::{BoxFuture, Driver, DriverConnection, QueryResult};
11use crate::postgres::connection::log_bindings;
12use crate::row::{Columns, Row};
13use crate::tls::TlsMode;
14use crate::value::Value;
15use rustlavel_core::events::Event;
16use rustlavel_core::{Error, Result};
17use std::sync::Arc;
18use std::time::Instant;
19use tokio::net::TcpStream;
20
21#[derive(Debug, Clone, Copy, Default)]
24pub struct SqlServerOptions {
25 pub encryption: Encryption,
26 pub tls: TlsOptions,
27}
28
29impl SqlServerOptions {
30 pub fn from_config(config: &DatabaseConfig) -> SqlServerOptions {
48 let trust = !matches!(config.tls_mode, TlsMode::VerifyCa | TlsMode::VerifyFull);
49 SqlServerOptions {
50 encryption: match config.tls_mode {
51 TlsMode::Disable => Encryption::Disabled,
52 _ => Encryption::Required,
53 },
54 tls: TlsOptions { trust_server_certificate: trust },
55 }
56 }
57}
58
59pub struct SqlServerConnection {
60 stream: TdsStream,
61 buffer: Vec<u8>,
63 config: DatabaseConfig,
64 packet_size: usize,
66 transaction: u64,
69 broken: bool,
71}
72
73impl SqlServerConnection {
74 pub async fn connect(config: &DatabaseConfig) -> Result<SqlServerConnection> {
76 SqlServerConnection::connect_with(config, SqlServerOptions::from_config(config)).await
77 }
78
79 pub async fn connect_with(
80 config: &DatabaseConfig,
81 options: SqlServerOptions,
82 ) -> Result<SqlServerConnection> {
83 let address = format!("{}:{}", config.host, config.port);
84
85 let socket = tokio::time::timeout(config.connect_timeout, TcpStream::connect(&address))
86 .await
87 .map_err(|_| {
88 Error::msg(format!(
89 "timed out connecting to {address}. Is SQL Server running, and is its TCP/IP \
90 protocol enabled? It is off by default on Windows."
91 ))
92 })?
93 .map_err(|e| {
94 Error::msg(format!(
95 "cannot connect to {}: {e}\n \
96 Check the host and port in DATABASE_URL; SQL Server listens on 1433 unless \
97 it was configured otherwise.",
98 config.redacted_url()
99 ))
100 })?;
101
102 let _ = socket.set_nodelay(true);
103
104 let mut connection = SqlServerConnection {
105 stream: TdsStream::Plain(socket),
106 buffer: Vec::with_capacity(8 * 1024),
107 config: config.clone(),
108 packet_size: DEFAULT_PACKET_SIZE,
109 transaction: 0,
110 broken: false,
111 };
112
113 let negotiated = connection.prelogin(options).await?;
114 connection.login(negotiated).await?;
115 Ok(connection)
116 }
117
118 pub fn is_broken(&self) -> bool {
119 self.broken
120 }
121
122 pub fn in_transaction(&self) -> bool {
125 self.transaction != 0
126 }
127
128 async fn prelogin(&mut self, options: SqlServerOptions) -> Result<Negotiated> {
130 self.write_message(packet::PRE_LOGIN, &protocol::prelogin(options.encryption.as_byte()))
131 .await?;
132
133 let response = protocol::parse_prelogin(&self.read_message().await?)?;
134 let negotiated = auth::negotiate(options.encryption, response.encryption)?;
135
136 if negotiated != Negotiated::None {
137 debug_assert!(self.buffer.is_empty());
141 let socket = match self.stream.take() {
142 TdsStream::Plain(socket) => socket,
143 _ => return Err(Error::Protocol("encryption was negotiated twice".into())),
144 };
145 let tls =
146 auth::start_tls(socket, &self.config.host, options.tls, self.packet_size).await?;
147 self.stream = TdsStream::Tls(Box::new(tls));
148 }
149
150 Ok(negotiated)
151 }
152
153 async fn login(&mut self, negotiated: Negotiated) -> Result<()> {
155 let password = auth::obfuscate_password(&self.config.password);
156 let hostname = hostname();
157
158 let payload = protocol::login7(&Login7 {
159 hostname: &hostname,
160 username: &self.config.user,
161 password: &password,
162 application: &self.config.application_name,
163 server: &self.config.host,
164 library: "rustlavel-db",
165 language: "",
166 database: &self.config.database,
167 packet_size: self.packet_size,
168 });
169
170 self.write_message(packet::LOGIN7, &payload).await?;
171
172 if negotiated == Negotiated::LoginOnly {
175 self.stream = self.stream.take().into_plain()?;
176 }
177
178 let message = self.read_message().await?;
179 let mut stream = TokenStream::new(&message);
180 let mut failure: Option<ServerError> = None;
181 let mut acknowledged = false;
182
183 while let Some(token) = stream.next_token()? {
184 match token {
185 Token::LoginAck(_) => acknowledged = true,
186 Token::EnvChange(change) => self.apply(change),
187 Token::Error(error) => failure = Some(error),
188 Token::Info(_) | Token::Done(_) | Token::Ignored(_) => {}
189 other => {
190 return Err(Error::Protocol(format!(
191 "unexpected token during login: {other:?}"
192 )));
193 }
194 }
195 }
196
197 if let Some(error) = failure {
198 self.broken = true;
199 return Err(login_error(error, &self.config));
200 }
201 if !acknowledged {
202 self.broken = true;
203 return Err(Error::Protocol(
204 "the server ended the login exchange without accepting or refusing it. If it \
205 requires Windows authentication, this driver implements SQL Server \
206 authentication only."
207 .into(),
208 ));
209 }
210
211 Ok(())
212 }
213
214 pub async fn simple_query(&mut self, sql: &str) -> Result<QueryResult> {
220 let started = Instant::now();
221 let payload = protocol::sql_batch(sql, self.transaction);
222 self.write_message(packet::SQL_BATCH, &payload).await?;
223
224 let result = self.collect(sql).await;
225 self.record(sql, &[], started, &result);
226 result
227 }
228
229 pub async fn query(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
237 let started = Instant::now();
238 let payload = protocol::execute_sql(sql, params, self.transaction);
239 self.write_message(packet::RPC, &payload).await?;
240
241 let result = self.collect(sql).await;
242 self.record(sql, params, started, &result);
243 result
244 }
245
246 async fn collect(&mut self, sql: &str) -> Result<QueryResult> {
248 let message = self.read_message().await?;
249 let mut stream = TokenStream::new(&message);
250
251 let mut columns: Columns = Arc::new(Vec::new());
252 let mut result = QueryResult::default();
253 let mut failure: Option<ServerError> = None;
254 let mut changes = Vec::new();
255
256 while let Some(token) = stream.next_token()? {
257 match token {
258 Token::ColumnMetadata(described) => {
259 columns = Arc::new(described.iter().map(|c| c.name.clone()).collect());
260 }
261 Token::Row(values) => result.rows.push(Row::new(Arc::clone(&columns), values)),
262 Token::Done(done) => {
263 if done.has_count() {
267 result.affected = done.rows;
268 }
269 }
270 Token::Error(error) => failure = Some(error),
271 Token::EnvChange(change) => changes.push(change),
273 Token::Info(_) | Token::LoginAck(_) | Token::ReturnStatus(_)
274 | Token::Ignored(_) => {}
275 }
276 }
277
278 for change in changes {
279 self.apply(change);
280 }
281
282 match failure {
283 Some(error) => Err(error.into_error(Some(sql))),
284 None => {
285 result.last_insert_id = generated_key(sql, &result.rows);
286 Ok(result)
287 }
288 }
289 }
290
291 fn apply(&mut self, change: EnvChange) {
292 match change {
293 EnvChange::PacketSize(size) => {
294 self.packet_size = size.clamp(HEADER_LEN + 1, 32 * 1024)
295 }
296 EnvChange::BeginTransaction(descriptor) => self.transaction = descriptor,
297 EnvChange::CommitTransaction | EnvChange::RollbackTransaction => self.transaction = 0,
298 EnvChange::Database(_) | EnvChange::Other(_) => {}
299 }
300 }
301
302 fn record(&self, sql: &str, params: &[Value], started: Instant, result: &Result<QueryResult>) {
304 let elapsed = started.elapsed();
305
306 if rustlavel_core::events::has_subscribers() {
307 let bindings = if log_bindings() {
308 params.iter().map(Value::to_display).collect::<Vec<_>>().join(", ")
309 } else {
310 format!("{} value(s) hidden", params.len())
311 };
312 Event::new("db.query")
313 .with("sql", sql)
314 .with("bindings", bindings)
315 .with("rows", result.as_ref().map(|r| r.rows.len()).unwrap_or(0))
316 .with("ok", result.is_ok())
317 .took(elapsed)
318 .dispatch();
319 }
320
321 rustlavel_core::debug!("db: {sql} ({:.1}ms)", elapsed.as_secs_f64() * 1000.0);
322 }
323
324 async fn write_message(&mut self, kind: u8, payload: &[u8]) -> Result<()> {
329 for packet in protocol::split_message(kind, payload, self.packet_size) {
330 if let Err(e) = self.stream.write_all(&packet).await {
331 self.broken = true;
332 return Err(Error::Io(e));
333 }
334 if let Err(e) = self.stream.flush().await {
335 self.broken = true;
336 return Err(Error::Io(e));
337 }
338 }
339 Ok(())
340 }
341
342 async fn read_message(&mut self) -> Result<Vec<u8>> {
345 let mut payload = Vec::new();
346
347 loop {
348 self.fill_to(HEADER_LEN).await?;
349 let header = PacketHeader::parse(&self.buffer)?;
350 let total = header.length as usize;
351
352 if total < HEADER_LEN {
353 self.broken = true;
354 return Err(Error::Protocol("packet length is impossibly small".into()));
355 }
356
357 self.fill_to(total).await?;
358 payload.extend_from_slice(&self.buffer[HEADER_LEN..total]);
359 self.buffer.drain(..total);
360
361 if header.is_end_of_message() {
362 return Ok(payload);
363 }
364 }
365 }
366
367 async fn fill_to(&mut self, wanted: usize) -> Result<()> {
369 while self.buffer.len() < wanted {
370 let mut chunk = [0u8; 8192];
371 let read = match self.stream.read(&mut chunk).await {
372 Ok(read) => read,
373 Err(e) => {
374 self.broken = true;
375 return Err(Error::Io(e));
376 }
377 };
378 if read == 0 {
379 self.broken = true;
380 return Err(Error::Protocol(
381 "the database closed the connection unexpectedly".into(),
382 ));
383 }
384 self.buffer.extend_from_slice(&chunk[..read]);
385 }
386 Ok(())
387 }
388
389 pub async fn close(mut self) {
394 let _ = self.stream.shutdown().await;
395 }
396}
397
398pub struct SqlServerDriver {
400 config: DatabaseConfig,
401 options: SqlServerOptions,
402 dialect: Arc<dyn Dialect>,
403}
404
405impl SqlServerDriver {
406 pub fn new(config: DatabaseConfig) -> Self {
407 let options = SqlServerOptions::from_config(&config);
408 SqlServerDriver::with_options(config, options)
409 }
410
411 pub fn with_options(config: DatabaseConfig, options: SqlServerOptions) -> Self {
412 SqlServerDriver {
413 config,
414 options,
415 dialect: Arc::new(crate::dialect::SqlServer),
416 }
417 }
418
419 pub fn config(&self) -> &DatabaseConfig {
420 &self.config
421 }
422
423 pub fn options(&self) -> &SqlServerOptions {
424 &self.options
425 }
426}
427
428impl Driver for SqlServerDriver {
429 fn generation(&self) -> u64 {
430 self.config.generation()
431 }
432
433 fn dialect(&self) -> Arc<dyn Dialect> {
434 Arc::clone(&self.dialect)
435 }
436
437 fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
438 Box::pin(async move {
439 let connection = SqlServerConnection::connect_with(&self.config.resolved(), self.options).await?;
440 Ok(Box::new(connection) as Box<dyn DriverConnection>)
441 })
442 }
443
444 fn describe(&self) -> String {
445 self.config.redacted_url()
446 }
447
448 fn max_connections(&self) -> usize {
449 self.config.max_connections
450 }
451}
452
453impl DriverConnection for SqlServerConnection {
454 fn query<'a>(
455 &'a mut self,
456 sql: &'a str,
457 params: &'a [Value],
458 ) -> BoxFuture<'a, Result<QueryResult>> {
459 Box::pin(SqlServerConnection::query(self, sql, params))
460 }
461
462 fn simple_query<'a>(&'a mut self, sql: &'a str) -> BoxFuture<'a, Result<QueryResult>> {
463 Box::pin(SqlServerConnection::simple_query(self, sql))
464 }
465
466 fn is_broken(&self) -> bool {
467 SqlServerConnection::is_broken(self)
468 }
469
470 fn in_transaction(&self) -> bool {
471 SqlServerConnection::in_transaction(self)
472 }
473
474 fn close(self: Box<Self>) -> BoxFuture<'static, ()> {
475 Box::pin(async move { SqlServerConnection::close(*self).await })
476 }
477}
478
479fn generated_key(sql: &str, rows: &[Row]) -> Option<i64> {
486 if !sql.to_ascii_lowercase().contains("output inserted") {
487 return None;
488 }
489 rows.first()?.get_at::<i64>(0).ok()
490}
491
492fn hostname() -> String {
494 std::env::var("HOSTNAME")
495 .ok()
496 .filter(|name| !name.is_empty())
497 .unwrap_or_else(|| "rustlavel".to_string())
498}
499
500fn login_error(error: ServerError, config: &DatabaseConfig) -> Error {
506 let number = error.number;
507 let base = error.into_error(None);
508
509 let advice = match number {
510 18456 => Some(format!(
511 "The password for `{}` was rejected, or that login does not exist. Check \
512 DATABASE_URL in your .env. SQL Server logs the real reason in its error log; the \
513 wire deliberately does not carry it.",
514 config.user
515 )),
516 4060 => Some(format!(
517 "Database `{}` cannot be opened by `{}`. Create it, grant access to it, or point \
518 DATABASE_URL at one that exists.",
519 config.database, config.user
520 )),
521 18452 => Some(
522 "The login is from an untrusted domain and cannot be used with Windows \
523 authentication. This driver implements SQL Server authentication only: give \
524 DATABASE_URL a username and password."
525 .to_string(),
526 ),
527 _ => None,
528 };
529
530 match advice {
531 Some(advice) => Error::msg(format!("{base}\n {advice}")),
532 None => base,
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 fn row(value: Value) -> Row {
541 Row::new(Arc::new(vec!["id".to_string()]), vec![value])
542 }
543
544 #[test]
545 fn a_generated_key_is_read_only_from_a_statement_that_asked_for_one() {
546 let rows = vec![row(Value::Int(42))];
547
548 assert_eq!(
549 generated_key("insert into [t] ([n]) output inserted.[id] values (@P1)", &rows),
550 Some(42)
551 );
552 assert_eq!(generated_key("INSERT INTO [t] OUTPUT INSERTED.[id] ...", &rows), Some(42));
554
555 assert_eq!(generated_key("select id from t", &rows), None);
557 assert_eq!(generated_key("insert ... output inserted.[id] ...", &[]), None);
559 assert_eq!(
561 generated_key("output inserted.[id]", &[row(Value::Text("abc".into()))]),
562 None
563 );
564 }
565
566 #[test]
567 fn a_rejected_password_says_where_to_look() {
568 let error = ServerError {
569 number: 18456,
570 severity: 14,
571 state: 1,
572 message: "Login failed for user 'ada'.".into(),
573 ..ServerError::default()
574 };
575 let config = DatabaseConfig { user: "ada".into(), ..DatabaseConfig::default() };
576
577 let rendered = login_error(error, &config).to_string();
578 assert!(rendered.contains("18456"), "{rendered}");
579 assert!(rendered.contains("severity 14"), "{rendered}");
580 assert!(rendered.contains("DATABASE_URL"), "{rendered}");
581 assert!(rendered.contains("`ada`"), "{rendered}");
582 }
583
584 #[test]
585 fn a_missing_database_names_the_database_that_is_missing() {
586 let error = ServerError {
587 number: 4060,
588 severity: 11,
589 message: "Cannot open database \"blog\" requested by the login.".into(),
590 ..ServerError::default()
591 };
592 let config = DatabaseConfig { database: "blog".into(), ..DatabaseConfig::default() };
593
594 let rendered = login_error(error, &config).to_string();
595 assert!(rendered.contains("`blog` cannot be opened"), "{rendered}");
596 }
597
598 #[test]
599 fn windows_authentication_is_refused_by_name_rather_than_retried() {
600 let error = ServerError { number: 18452, severity: 14, ..ServerError::default() };
601
602 let rendered = login_error(error, &DatabaseConfig::default()).to_string();
603 assert!(rendered.contains("SQL Server authentication only"), "{rendered}");
604 }
605
606 #[test]
607 fn an_error_with_no_advice_is_still_reported_verbatim() {
608 let error = ServerError {
609 number: 208,
610 severity: 16,
611 message: "Invalid object name 'nope'.".into(),
612 ..ServerError::default()
613 };
614
615 let rendered = login_error(error, &DatabaseConfig::default()).to_string();
616 assert!(rendered.contains("Invalid object name"), "{rendered}");
617 }
618
619 #[test]
620 fn the_driver_speaks_the_sql_server_dialect_and_never_prints_the_password() {
621 let config = DatabaseConfig {
622 driver: "sqlserver".into(),
623 user: "sa".into(),
624 password: "hunter2".into(),
625 port: 1433,
626 ..DatabaseConfig::default()
627 };
628 let driver = SqlServerDriver::new(config);
629
630 assert_eq!(driver.dialect().name(), "sqlserver");
631 assert_eq!(driver.options().encryption, Encryption::Required);
632 assert!(!driver.describe().contains("hunter2"), "{}", driver.describe());
633 }
634
635 #[tokio::test]
636 async fn connecting_to_a_closed_port_names_the_server_and_the_default_port() {
637 let config = DatabaseConfig {
638 driver: "sqlserver".into(),
639 port: 1,
640 password: "hunter2".into(),
641 ..DatabaseConfig::default()
642 };
643
644 let error = match SqlServerConnection::connect(&config).await {
645 Err(error) => error.to_string(),
646 Ok(_) => panic!("nothing should be listening on port 1"),
647 };
648
649 assert!(error.contains("127.0.0.1:1"), "{error}");
650 assert!(error.contains("1433"), "{error}");
651 assert!(!error.contains("hunter2"), "{error}");
653 }
654}