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