tdsql 0.4.1

Ergonomic async Microsoft SQL Server client for Rust, with typed rows and multi-result-set support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
//! A blocking client, for callers that are not async.
//!
//! Enable the `blocking` feature to get it:
//!
//! ```toml
//! [dependencies]
//! tdsql = { version = "0.1", features = ["blocking"] }
//! ```
//!
//! It is the same client with the `async` taken off: each call drives the
//! asynchronous one to completion on a runtime the client owns.
//!
//! ```no_run
//! use tdsql::blocking::Client;
//! use tdsql::Config;
//!
//! # fn main() -> tdsql::Result<()> {
//! let mut client = Client::connect(
//!     &Config::new()
//!         .host("localhost")
//!         .database("master")
//!         .auth("sa", "YourStrong!Passw0rd")
//!         .trust_cert(),
//! )?;
//!
//! let rows = client.query("SELECT id, name FROM users WHERE id > @P1", &[&10i32])?;
//! for row in &rows {
//!     let id: i32 = row.get("id");
//!     println!("{id}");
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Do not call this from async code
//!
//! Each client owns a runtime, and driving a runtime from inside another one
//! panics. [`Client::connect`] detects that case up front and returns
//! [`Error::BlockingInAsync`] rather than letting a later call panic. In async
//! code, use [`tdsql::Client`](crate::Client) directly.

use tokio::runtime::{Builder, Runtime};

use crate::command::Command;
use crate::config::Config;
use crate::dataset::DataSet;
use crate::error::{Error, Result};
use crate::row::Row;
use crate::transaction::IsolationLevel;
use crate::value::{FromSql, ToSql};

fn build_runtime() -> Result<Runtime> {
    // A dedicated worker thread means the connection task keeps making
    // progress between calls, so a queued rollback is sent promptly rather
    // than waiting for the next statement.
    Builder::new_multi_thread()
        .worker_threads(1)
        .enable_all()
        .thread_name("tdsql-connection")
        .build()
        .map_err(Error::Io)
}

/// A blocking connection to SQL Server.
///
/// The blocking counterpart to [`tdsql::Client`](crate::Client); every method
/// mirrors it without `async`/`await`.
pub struct Client {
    runtime: Runtime,
    inner: crate::Client,
}

impl std::fmt::Debug for Client {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("blocking::Client")
            .field("inner", &self.inner)
            .finish()
    }
}

impl Client {
    /// Connect using this configuration.
    ///
    /// Fails with [`Error::BlockingInAsync`] if called from inside an async
    /// runtime.
    pub fn connect(config: &Config) -> Result<Self> {
        if tokio::runtime::Handle::try_current().is_ok() {
            return Err(Error::BlockingInAsync);
        }
        let runtime = build_runtime()?;
        let inner = runtime.block_on(crate::Client::connect(config))?;
        Ok(Self { runtime, inner })
    }

    /// Connect using an ADO.NET-style connection string.
    pub fn connect_str(connection_string: &str) -> Result<Self> {
        Self::connect(&Config::from_ado_string(connection_string))
    }

    /// Run a query and collect every row of the first result set.
    pub fn query(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Vec<Row>> {
        self.runtime.block_on(self.inner.query(sql, params))
    }

    /// Run a query expecting exactly one row.
    pub fn query_one(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Row> {
        self.runtime.block_on(self.inner.query_one(sql, params))
    }

    /// Run a query expecting at most one row.
    pub fn query_opt(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Option<Row>> {
        self.runtime.block_on(self.inner.query_opt(sql, params))
    }

    /// Run a query and read the first column of the first row.
    pub fn query_scalar<T: FromSql>(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<T> {
        self.runtime.block_on(self.inner.query_scalar(sql, params))
    }

    /// Run a statement and report how many rows it affected.
    pub fn execute(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<u64> {
        self.runtime.block_on(self.inner.execute(sql, params))
    }

    /// Run a SQL batch verbatim, with no parameters, and collect its rows.
    ///
    /// Use this for DDL; see [`tdsql::Client::batch`](crate::Client::batch).
    pub fn batch(&mut self, sql: &str) -> Result<Vec<Row>> {
        self.runtime.block_on(self.inner.batch(sql))
    }

    /// Run a SQL batch verbatim and collect every result set.
    pub fn batch_dataset(&mut self, sql: &str) -> Result<DataSet> {
        self.runtime.block_on(self.inner.batch_dataset(sql))
    }

    /// Run a [`Command`] and collect every result set it produces.
    pub fn query_dataset(&mut self, command: &Command) -> Result<DataSet> {
        self.runtime.block_on(self.inner.query_dataset(command))
    }

    /// Run a [`Command`] for its row count rather than its rows.
    pub fn execute_command(&mut self, command: &Command) -> Result<u64> {
        self.runtime.block_on(self.inner.execute_command(command))
    }

    /// Begin a transaction at the server's default isolation level.
    ///
    /// ```no_run
    /// # use tdsql::blocking::Client;
    /// # fn f(client: &mut Client) -> tdsql::Result<()> {
    /// let mut tx = client.transaction()?;
    /// tx.execute("INSERT INTO orders (id) VALUES (@P1)", &[&1i32])?;
    /// tx.commit()?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn transaction(&mut self) -> Result<Transaction<'_>> {
        self.transaction_with_isolation(IsolationLevel::Unspecified)
    }

    /// Begin a transaction at an explicit isolation level.
    pub fn transaction_with_isolation(&mut self, level: IsolationLevel) -> Result<Transaction<'_>> {
        let Self { runtime, inner } = self;
        let tx = runtime.block_on(inner.transaction_with_isolation(level))?;
        Ok(Transaction { runtime, inner: tx })
    }

    /// Close the connection.
    pub fn close(self) -> Result<()> {
        let Self { runtime, inner } = self;
        runtime.block_on(inner.close())
    }
}

/// An in-progress transaction on a blocking [`Client`].
///
/// Behaves exactly like [`tdsql::Transaction`](crate::Transaction): commit to
/// keep the work, and **dropping without committing rolls back**.
pub struct Transaction<'a> {
    runtime: &'a Runtime,
    inner: crate::Transaction<'a>,
}

impl std::fmt::Debug for Transaction<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("blocking::Transaction")
            .field("inner", &self.inner)
            .finish()
    }
}

impl Transaction<'_> {
    /// Commit the work.
    pub fn commit(self) -> Result<()> {
        let Self { runtime, inner } = self;
        runtime.block_on(inner.commit())
    }

    /// Discard the work, reporting any error.
    pub fn rollback(self) -> Result<()> {
        let Self { runtime, inner } = self;
        runtime.block_on(inner.rollback())
    }

    /// Open a nested transaction, backed by an automatically named savepoint.
    pub fn transaction(&mut self) -> Result<Transaction<'_>> {
        let Self { runtime, inner } = self;
        let tx = runtime.block_on(inner.transaction())?;
        Ok(Transaction { runtime, inner: tx })
    }

    /// Open a nested transaction at a named savepoint.
    pub fn savepoint(&mut self, name: impl Into<String>) -> Result<Transaction<'_>> {
        let Self { runtime, inner } = self;
        let tx = runtime.block_on(inner.savepoint(name))?;
        Ok(Transaction { runtime, inner: tx })
    }

    /// Run a query and collect every row of the first result set.
    pub fn query(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Vec<Row>> {
        self.runtime.block_on(self.inner.query(sql, params))
    }

    /// Run a query expecting exactly one row.
    pub fn query_one(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Row> {
        self.runtime.block_on(self.inner.query_one(sql, params))
    }

    /// Run a query expecting at most one row.
    pub fn query_opt(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Option<Row>> {
        self.runtime.block_on(self.inner.query_opt(sql, params))
    }

    /// Run a query and read the first column of the first row.
    pub fn query_scalar<T: FromSql>(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<T> {
        self.runtime.block_on(self.inner.query_scalar(sql, params))
    }

    /// Run a statement and report how many rows it affected.
    pub fn execute(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<u64> {
        self.runtime.block_on(self.inner.execute(sql, params))
    }

    /// Run a SQL batch verbatim and collect its rows.
    pub fn batch(&mut self, sql: &str) -> Result<Vec<Row>> {
        self.runtime.block_on(self.inner.batch(sql))
    }

    /// Run a SQL batch verbatim and collect every result set.
    pub fn batch_dataset(&mut self, sql: &str) -> Result<DataSet> {
        self.runtime.block_on(self.inner.batch_dataset(sql))
    }

    /// Run a [`Command`] and collect every result set it produces.
    pub fn query_dataset(&mut self, command: &Command) -> Result<DataSet> {
        self.runtime.block_on(self.inner.query_dataset(command))
    }

    /// Run a [`Command`] for its row count rather than its rows.
    pub fn execute_command(&mut self, command: &Command) -> Result<u64> {
        self.runtime.block_on(self.inner.execute_command(command))
    }
}

mod sealed {
    use tokio::runtime::Runtime;

    /// Grants the default method bodies the runtime and the async connection
    /// underneath.
    ///
    /// Deliberately not public: handing out the inner client from a
    /// `Transaction` would let a caller commit or roll back behind the
    /// transaction's back.
    pub trait Sealed {
        fn parts(&mut self) -> (&Runtime, &mut crate::Client);
    }
}

pub(crate) use sealed::Sealed;

impl Sealed for Client {
    fn parts(&mut self) -> (&Runtime, &mut crate::Client) {
        let Self { runtime, inner } = self;
        (runtime, inner)
    }
}

impl Sealed for Transaction<'_> {
    fn parts(&mut self) -> (&Runtime, &mut crate::Client) {
        let Self { runtime, inner } = self;
        (runtime, inner.client_mut())
    }
}

impl<T: Sealed + ?Sized> Sealed for &mut T {
    fn parts(&mut self) -> (&Runtime, &mut crate::Client) {
        (**self).parts()
    }
}

/// Anything blocking statements can be run against: a [`Client`] or a
/// [`Transaction`].
///
/// The blocking counterpart to [`tdsql::Executor`](crate::Executor). Take
/// `&mut impl Executor` in a helper and the caller decides whether the work
/// lands on the connection directly or inside a transaction:
///
/// ```no_run
/// use tdsql::blocking::{Client, Executor};
/// use tdsql::{Result, Row};
///
/// fn load_user(db: &mut impl Executor, id: i32) -> Result<Row> {
///     db.query_one("SELECT id, name FROM users WHERE id = @P1", &[&id])
/// }
///
/// # fn f(client: &mut Client) -> Result<()> {
/// // Straight to the connection...
/// let user = load_user(client, 1)?;
///
/// // ...or inside a transaction, unchanged.
/// let mut tx = client.transaction()?;
/// let user = load_user(&mut tx, 1)?;
/// tx.commit()?;
/// # Ok(())
/// # }
/// ```
///
/// The trait is sealed — it describes the two types this crate provides rather
/// than an extension point.
pub trait Executor: Sealed {
    /// Open a transaction scoped to this executor.
    ///
    /// On a [`Client`] this begins a real transaction. On a [`Transaction`] it
    /// opens a savepoint instead, so a helper that wants its own atomic scope
    /// nests correctly rather than trying to begin a second transaction on a
    /// connection that already has one.
    fn transaction(&mut self) -> Result<Transaction<'_>>;

    /// Run a query and collect every row of the first result set.
    fn query(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Vec<Row>> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.query(sql, params))
    }

    /// Run a query expecting exactly one row.
    fn query_one(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Row> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.query_one(sql, params))
    }

    /// Run a query expecting at most one row.
    fn query_opt(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Option<Row>> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.query_opt(sql, params))
    }

    /// Run a query and read the first column of the first row.
    fn query_scalar<T: FromSql>(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<T> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.query_scalar(sql, params))
    }

    /// Run a statement and report how many rows it affected.
    fn execute(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<u64> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.execute(sql, params))
    }

    /// Run a SQL batch verbatim and collect its rows.
    fn batch(&mut self, sql: &str) -> Result<Vec<Row>> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.batch(sql))
    }

    /// Run a SQL batch verbatim and collect every result set.
    fn batch_dataset(&mut self, sql: &str) -> Result<DataSet> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.batch_dataset(sql))
    }

    /// Run a [`Command`] and collect every result set it produces.
    fn query_dataset(&mut self, command: &Command) -> Result<DataSet> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.query_dataset(command))
    }

    /// Run a [`Command`] for its row count rather than its rows.
    fn execute_command(&mut self, command: &Command) -> Result<u64> {
        let (runtime, client) = self.parts();
        runtime.block_on(client.execute_command(command))
    }
}

impl Executor for Client {
    fn transaction(&mut self) -> Result<Transaction<'_>> {
        Client::transaction(self)
    }
}

impl Executor for Transaction<'_> {
    fn transaction(&mut self) -> Result<Transaction<'_>> {
        Transaction::transaction(self)
    }
}

impl<T: Executor + ?Sized> Executor for &mut T {
    fn transaction(&mut self) -> Result<Transaction<'_>> {
        (**self).transaction()
    }
}