Skip to main content

mssql_client/
state.rs

1//! Connection state types for type-state pattern.
2//!
3//! The type-state pattern ensures at compile time that certain operations
4//! can only be performed when the connection is in the appropriate state.
5//!
6//! ## State Transitions
7//!
8//! ```text
9//! Disconnected -> Connected (via TCP connect)
10//! Connected -> Ready (via authentication)
11//! Ready -> InTransaction (via begin_transaction())
12//! InTransaction -> Ready (via commit() or rollback())
13//! ```
14//!
15//! Queries do not change the connection state: `query()` borrows the client
16//! mutably and returns an iterable result.
17//!
18//! Incremental streaming (`query_stream` / `query_stream_blob`) likewise does
19//! **not** transition into a dedicated state. Rather than a dedicated
20//! streaming type-state, the streams borrow the client mutably
21//! (`&mut Client<S>`) for their lifetime: the borrow checker already enforces
22//! that no other request can run on the connection until the stream is dropped,
23//! and a `&mut` borrow — unlike a consuming state transition — works uniformly
24//! for both [`Ready`] and [`InTransaction`] clients and returns the borrow
25//! automatically.
26
27use std::marker::PhantomData;
28
29/// Marker trait for connection states.
30///
31/// This trait is sealed to prevent external implementations,
32/// ensuring that only the states defined in this crate are valid.
33pub trait ConnectionState: private::Sealed {}
34
35/// Connection is not yet established.
36///
37/// In this state, only `connect()` can be called.
38pub struct Disconnected;
39
40/// TCP connection established, awaiting authentication.
41///
42/// In this intermediate state:
43/// - TCP connection is open
44/// - TLS negotiation may be in progress or complete
45/// - Login/authentication has not yet completed
46///
47/// This state is mostly internal; users typically go directly from
48/// `Disconnected` to `Ready` via `Client::connect()`.
49pub struct Connected;
50
51/// Connection is established and ready for queries.
52///
53/// In this state, queries can be executed and transactions can be started.
54pub struct Ready;
55
56/// Connection is in a transaction.
57///
58/// In this state, queries execute within the transaction context.
59/// The transaction must be explicitly committed or rolled back.
60pub struct InTransaction;
61
62impl ConnectionState for Disconnected {}
63impl ConnectionState for Connected {}
64impl ConnectionState for Ready {}
65impl ConnectionState for InTransaction {}
66
67mod private {
68    pub trait Sealed {}
69    impl Sealed for super::Disconnected {}
70    impl Sealed for super::Connected {}
71    impl Sealed for super::Ready {}
72    impl Sealed for super::InTransaction {}
73}
74
75/// Type-level state transition marker.
76///
77/// This is used internally to track state transitions at compile time.
78#[derive(Debug)]
79pub struct StateMarker<S: ConnectionState> {
80    _state: PhantomData<S>,
81}
82
83impl<S: ConnectionState> StateMarker<S> {
84    pub(crate) fn new() -> Self {
85        Self {
86            _state: PhantomData,
87        }
88    }
89}
90
91impl<S: ConnectionState> Default for StateMarker<S> {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl<S: ConnectionState> Clone for StateMarker<S> {
98    fn clone(&self) -> Self {
99        *self
100    }
101}
102
103impl<S: ConnectionState> Copy for StateMarker<S> {}
104
105/// Internal protocol state for runtime management.
106///
107/// While connection states are tracked at compile-time via type-state,
108/// the protocol layer has runtime state that must be managed.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[non_exhaustive]
111pub enum ProtocolState {
112    /// Awaiting response from server.
113    AwaitingResponse,
114    /// Processing token stream.
115    ProcessingTokens,
116    /// Draining remaining tokens after cancellation.
117    Draining,
118}
119
120impl Default for ProtocolState {
121    fn default() -> Self {
122        Self::AwaitingResponse
123    }
124}
125
126impl ProtocolState {
127    /// Check if the connection is in a usable state.
128    ///
129    /// Currently all protocol states are usable. This method exists for
130    /// forward compatibility if a broken/poisoned state is added later.
131    #[must_use]
132    pub fn is_usable(&self) -> bool {
133        // All current states are usable
134        matches!(
135            self,
136            Self::AwaitingResponse | Self::ProcessingTokens | Self::Draining
137        )
138    }
139
140    /// Check if the connection is actively processing.
141    #[must_use]
142    pub fn is_busy(&self) -> bool {
143        matches!(self, Self::ProcessingTokens | Self::Draining)
144    }
145}