Skip to main content

rustlavel_db/sqlserver/
connection.rs

1//! A single SQL Server connection.
2
3use 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/// Settings that are specific to SQL Server and have no home in
21/// [`DatabaseConfig`], which is shared with the other drivers.
22#[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    /// Bytes read from the socket but not yet consumed as a packet.
31    buffer: Vec<u8>,
32    config: DatabaseConfig,
33    /// The largest packet either side may send, as the server settled it.
34    packet_size: usize,
35    /// The descriptor every request must quote; zero when no transaction is
36    /// open, which is also how `in_transaction` is answered.
37    transaction: u64,
38    /// Set when the connection is known to be unusable, so the pool discards it.
39    broken: bool,
40}
41
42impl SqlServerConnection {
43    /// Open a connection: pre-login, encryption, then login.
44    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    /// True while a transaction is open, so the pool never hands back a
92    /// connection that would leak one.
93    pub fn in_transaction(&self) -> bool {
94        self.transaction != 0
95    }
96
97    /// Exchange PRELOGIN packets and put TLS up if either side asked for it.
98    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            // The socket is handed to the handshake wrapper whole; nothing can
107            // be buffered here, because PRELOGIN is one message and it has
108            // already been consumed in full.
109            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    /// Send LOGIN7 and read the response through to its DONE.
123    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        // ENCRYPT_OFF means exactly this: the login packet was the only thing
142        // encrypted, and the response already comes back in the clear.
143        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    /// Run a statement with no parameters, as a batch.
184    ///
185    /// Used for DDL and for transaction control, both of which have to run
186    /// outside `sp_executesql` — a `begin transaction` inside a procedure ends
187    /// when the procedure does.
188    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    /// Run a statement through `sp_executesql`.
199    ///
200    /// Every statement takes this route, parameters or not, because it is the
201    /// route where a bound value is a value: the statement text and the
202    /// parameter declarations are themselves arguments to a stored procedure,
203    /// so nothing a caller binds is ever concatenated into SQL. A value cannot
204    /// change the shape of a statement it was never part of.
205    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    /// Read the whole response and turn it into rows, a count and an error.
216    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                    // Several DONE tokens arrive per call — one per statement
233                    // inside the procedure, one for the procedure. Only those
234                    // carrying DONE_COUNT have a number worth believing.
235                    if done.has_count() {
236                        result.affected = done.rows;
237                    }
238                }
239                Token::Error(error) => failure = Some(error),
240                // Applied after the loop, because `stream` borrows the message.
241                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    /// Publish the query on the event bus for Telescope and slow-query logging.
272    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    /// Frame a payload into packets and send it.
294    ///
295    /// One write per packet, so an encrypted connection puts each packet in its
296    /// own TLS record — see [`protocol::split_message`] for why that matters.
297    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    /// Read packets until one carries the end-of-message bit, returning the
312    /// payloads joined back together.
313    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    /// Read from the socket until the buffer holds at least `wanted` bytes.
337    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    /// Hang up.
359    ///
360    /// TDS has no goodbye token — MS-TDS says a client ends a session by
361    /// closing the transport — so there is nothing to send first.
362    pub async fn close(mut self) {
363        let _ = self.stream.shutdown().await;
364    }
365}
366
367/// Opens SQL Server connections.
368pub 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
447/// The key an `output inserted` clause handed back.
448///
449/// SQL Server returns a generated key as an ordinary row rather than in the
450/// acknowledgement, so there is nothing on the wire that says "this is the
451/// identity". The statement is what says so: the dialect emits `output
452/// inserted.[id]`, and only a statement carrying that clause has a key to read.
453fn 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
460/// The host name to send in LOGIN7, which shows up in `sys.dm_exec_sessions`.
461fn hostname() -> String {
462    std::env::var("HOSTNAME")
463        .ok()
464        .filter(|name| !name.is_empty())
465        .unwrap_or_else(|| "rustlavel".to_string())
466}
467
468/// Turn a login failure into something the developer can act on.
469///
470/// SQL Server's login errors are famously terse — 18456 says "Login failed for
471/// user" and nothing else, because saying more would help an attacker — so the
472/// actionable half has to come from this side.
473fn 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        // Case does not matter; the builder may emit either.
521        assert_eq!(generated_key("INSERT INTO [t] OUTPUT INSERTED.[id] ...", &rows), Some(42));
522
523        // A plain select returns rows too, and none of them is an identity.
524        assert_eq!(generated_key("select id from t", &rows), None);
525        // A statement that asked but got nothing back.
526        assert_eq!(generated_key("insert ... output inserted.[id] ...", &[]), None);
527        // A non-integer key, such as a uuid, is not an insert id.
528        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        // The password must never appear, even in a connection error.
620        assert!(!error.contains("hunter2"), "{error}");
621    }
622}