tiberius/
row.rs

1use crate::{
2    error::Error,
3    tds::codec::{ColumnData, FixedLenType, TokenRow, TypeInfo, VarLenType},
4    FromSql,
5};
6use std::{fmt::Display, sync::Arc};
7
8/// A column of data from a query.
9#[derive(Debug, Clone)]
10pub struct Column {
11    pub(crate) name: String,
12    pub(crate) column_type: ColumnType,
13}
14
15impl Column {
16    /// Construct a new Column.
17    pub fn new(name: String, column_type: ColumnType) -> Self {
18        Self { name, column_type }
19    }
20
21    /// The name of the column.
22    pub fn name(&self) -> &str {
23        &self.name
24    }
25
26    /// The type of the column.
27    pub fn column_type(&self) -> ColumnType {
28        self.column_type
29    }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33/// The type of the column.
34pub enum ColumnType {
35    /// The column doesn't have a specified type.
36    Null,
37    /// A bit or boolean value.
38    Bit,
39    /// An 8-bit integer value.
40    Int1,
41    /// A 16-bit integer value.
42    Int2,
43    /// A 32-bit integer value.
44    Int4,
45    /// A 64-bit integer value.
46    Int8,
47    /// A 32-bit datetime value.
48    Datetime4,
49    /// A 32-bit floating point value.
50    Float4,
51    /// A 64-bit floating point value.
52    Float8,
53    /// Money value.
54    Money,
55    /// A TDS 7.2 datetime value.
56    Datetime,
57    /// A 32-bit money value.
58    Money4,
59    /// A unique identifier, UUID.
60    Guid,
61    /// N-bit integer value (variable).
62    Intn,
63    /// A bit value in a variable-length type.
64    Bitn,
65    /// A decimal value (same as `Numericn`).
66    Decimaln,
67    /// A numeric value (same as `Decimaln`).
68    Numericn,
69    /// A n-bit floating point value.
70    Floatn,
71    /// A n-bit datetime value (TDS 7.2).
72    Datetimen,
73    /// A n-bit date value (TDS 7.3).
74    Daten,
75    /// A n-bit time value (TDS 7.3).
76    Timen,
77    /// A n-bit datetime2 value (TDS 7.3).
78    Datetime2,
79    /// A n-bit datetime value with an offset (TDS 7.3).
80    DatetimeOffsetn,
81    /// A variable binary value.
82    BigVarBin,
83    /// A large variable string value.
84    BigVarChar,
85    /// A binary value.
86    BigBinary,
87    /// A string value.
88    BigChar,
89    /// A variable string value with UTF-16 encoding.
90    NVarchar,
91    /// A string value with UTF-16 encoding.
92    NChar,
93    /// A XML value.
94    Xml,
95    /// User-defined type.
96    Udt,
97    /// A text value (deprecated).
98    Text,
99    /// A image value (deprecated).
100    Image,
101    /// A text value with UTF-16 encoding (deprecated).
102    NText,
103    /// An SQL variant type.
104    SSVariant,
105}
106
107impl From<&TypeInfo> for ColumnType {
108    fn from(ti: &TypeInfo) -> Self {
109        match ti {
110            TypeInfo::FixedLen(flt) => match flt {
111                FixedLenType::Int1 => Self::Int1,
112                FixedLenType::Bit => Self::Bit,
113                FixedLenType::Int2 => Self::Int2,
114                FixedLenType::Int4 => Self::Int4,
115                FixedLenType::Datetime4 => Self::Datetime4,
116                FixedLenType::Float4 => Self::Float4,
117                FixedLenType::Money => Self::Money,
118                FixedLenType::Datetime => Self::Datetime,
119                FixedLenType::Float8 => Self::Float8,
120                FixedLenType::Money4 => Self::Money4,
121                FixedLenType::Int8 => Self::Int8,
122                FixedLenType::Null => Self::Null,
123            },
124            TypeInfo::VarLenSized(cx) => match cx.r#type() {
125                VarLenType::Guid => Self::Guid,
126                VarLenType::Intn => match cx.len() {
127                    1 => Self::Int1,
128                    2 => Self::Int2,
129                    4 => Self::Int4,
130                    8 => Self::Int8,
131                    _ => Self::Intn,
132                },
133                VarLenType::Bitn => Self::Bitn,
134                VarLenType::Decimaln => Self::Decimaln,
135                VarLenType::Numericn => Self::Numericn,
136                VarLenType::Floatn => match cx.len() {
137                    4 => Self::Float4,
138                    8 => Self::Float8,
139                    _ => Self::Floatn,
140                },
141                VarLenType::Money => Self::Money,
142                VarLenType::Datetimen => Self::Datetimen,
143                #[cfg(feature = "tds73")]
144                VarLenType::Daten => Self::Daten,
145                #[cfg(feature = "tds73")]
146                VarLenType::Timen => Self::Timen,
147                #[cfg(feature = "tds73")]
148                VarLenType::Datetime2 => Self::Datetime2,
149                #[cfg(feature = "tds73")]
150                VarLenType::DatetimeOffsetn => Self::DatetimeOffsetn,
151                VarLenType::BigVarBin => Self::BigVarBin,
152                VarLenType::BigVarChar => Self::BigVarChar,
153                VarLenType::BigBinary => Self::BigBinary,
154                VarLenType::BigChar => Self::BigChar,
155                VarLenType::NVarchar => Self::NVarchar,
156                VarLenType::NChar => Self::NChar,
157                VarLenType::Xml => Self::Xml,
158                VarLenType::Udt => Self::Udt,
159                VarLenType::Text => Self::Text,
160                VarLenType::Image => Self::Image,
161                VarLenType::NText => Self::NText,
162                VarLenType::SSVariant => Self::SSVariant,
163            },
164            TypeInfo::VarLenSizedPrecision { ty, .. } => match ty {
165                VarLenType::Guid => Self::Guid,
166                VarLenType::Intn => Self::Intn,
167                VarLenType::Bitn => Self::Bitn,
168                VarLenType::Decimaln => Self::Decimaln,
169                VarLenType::Numericn => Self::Numericn,
170                VarLenType::Floatn => Self::Floatn,
171                VarLenType::Money => Self::Money,
172                VarLenType::Datetimen => Self::Datetimen,
173                #[cfg(feature = "tds73")]
174                VarLenType::Daten => Self::Daten,
175                #[cfg(feature = "tds73")]
176                VarLenType::Timen => Self::Timen,
177                #[cfg(feature = "tds73")]
178                VarLenType::Datetime2 => Self::Datetime2,
179                #[cfg(feature = "tds73")]
180                VarLenType::DatetimeOffsetn => Self::DatetimeOffsetn,
181                VarLenType::BigVarBin => Self::BigVarBin,
182                VarLenType::BigVarChar => Self::BigVarChar,
183                VarLenType::BigBinary => Self::BigBinary,
184                VarLenType::BigChar => Self::BigChar,
185                VarLenType::NVarchar => Self::NVarchar,
186                VarLenType::NChar => Self::NChar,
187                VarLenType::Xml => Self::Xml,
188                VarLenType::Udt => Self::Udt,
189                VarLenType::Text => Self::Text,
190                VarLenType::Image => Self::Image,
191                VarLenType::NText => Self::NText,
192                VarLenType::SSVariant => Self::SSVariant,
193            },
194            TypeInfo::Xml { .. } => Self::Xml,
195        }
196    }
197}
198
199/// A row of data from a query.
200///
201/// Data can be accessed either by copying through [`get`] or [`try_get`]
202/// methods, or moving by value using the [`IntoIterator`] implementation.
203///
204/// ```
205/// # use tiberius::{Config, FromSqlOwned};
206/// # use tokio_util::compat::TokioAsyncWriteCompatExt;
207/// # use std::env;
208/// # #[tokio::main]
209/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
210/// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
211/// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
212/// # );
213/// # let config = Config::from_ado_string(&c_str)?;
214/// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
215/// # tcp.set_nodelay(true)?;
216/// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
217/// // by-reference
218/// let row = client
219///     .query("SELECT @P1 AS col1", &[&"test"])
220///     .await?
221///     .into_row()
222///     .await?
223///     .unwrap();
224///
225/// assert_eq!(Some("test"), row.get("col1"));
226///
227/// // ...or by-value
228/// let row = client
229///     .query("SELECT @P1 AS col1", &[&"test"])
230///     .await?
231///     .into_row()
232///     .await?
233///     .unwrap();
234///
235/// for val in row.into_iter() {
236///     assert_eq!(
237///         Some(String::from("test")),
238///         String::from_sql_owned(val)?
239///     )
240/// }
241/// # Ok(())
242/// # }
243/// ```
244///
245/// [`get`]: #method.get
246/// [`try_get`]: #method.try_get
247/// [`IntoIterator`]: #impl-IntoIterator
248#[derive(Debug)]
249pub struct Row {
250    pub(crate) columns: Arc<Vec<Column>>,
251    pub(crate) data: TokenRow<'static>,
252    pub(crate) result_index: usize,
253}
254
255pub trait QueryIdx
256where
257    Self: Display,
258{
259    fn idx(&self, row: &Row) -> Option<usize>;
260}
261
262impl QueryIdx for usize {
263    fn idx(&self, _row: &Row) -> Option<usize> {
264        Some(*self)
265    }
266}
267
268impl QueryIdx for &str {
269    fn idx(&self, row: &Row) -> Option<usize> {
270        row.columns.iter().position(|c| c.name() == *self)
271    }
272}
273
274impl Row {
275    /// Columns defining the row data. Columns listed here are in the same order
276    /// as the resulting data.
277    ///
278    /// # Example
279    ///
280    /// ```
281    /// # use tiberius::Config;
282    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
283    /// # use std::env;
284    /// # #[tokio::main]
285    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
286    /// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
287    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
288    /// # );
289    /// # let config = Config::from_ado_string(&c_str)?;
290    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
291    /// # tcp.set_nodelay(true)?;
292    /// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
293    /// let row = client
294    ///     .query("SELECT 1 AS foo, 2 AS bar", &[])
295    ///     .await?
296    ///     .into_row()
297    ///     .await?
298    ///     .unwrap();
299    ///
300    /// assert_eq!("foo", row.columns()[0].name());
301    /// assert_eq!("bar", row.columns()[1].name());
302    /// # Ok(())
303    /// # }
304    /// ```
305    pub fn columns(&self) -> &[Column] {
306        &self.columns
307    }
308
309    /// Return an iterator over row column-value pairs.
310    pub fn cells(&self) -> impl Iterator<Item = (&Column, &ColumnData<'static>)> {
311        self.columns().iter().zip(self.data.iter())
312    }
313
314    /// The result set number, starting from zero and increasing if the stream
315    /// has results from more than one query.
316    pub fn result_index(&self) -> usize {
317        self.result_index
318    }
319
320    /// Returns the number of columns in the row.
321    ///
322    /// # Example
323    ///
324    /// ```
325    /// # use tiberius::Config;
326    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
327    /// # use std::env;
328    /// # #[tokio::main]
329    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
330    /// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
331    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
332    /// # );
333    /// # let config = Config::from_ado_string(&c_str)?;
334    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
335    /// # tcp.set_nodelay(true)?;
336    /// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
337    /// let row = client
338    ///     .query("SELECT 1, 2", &[])
339    ///     .await?
340    ///     .into_row()
341    ///     .await?
342    ///     .unwrap();
343    ///
344    /// assert_eq!(2, row.len());
345    /// # Ok(())
346    /// # }
347    /// ```
348    #[allow(clippy::len_without_is_empty)]
349    pub fn len(&self) -> usize {
350        self.data.len()
351    }
352
353    /// Retrieve a column value for a given column index, which can either be
354    /// the zero-indexed position or the name of the column.
355    ///
356    /// # Example
357    ///
358    /// ```
359    /// # use tiberius::Config;
360    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
361    /// # use std::env;
362    /// # #[tokio::main]
363    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
364    /// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
365    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
366    /// # );
367    /// # let config = Config::from_ado_string(&c_str)?;
368    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
369    /// # tcp.set_nodelay(true)?;
370    /// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
371    /// let row = client
372    ///     .query("SELECT @P1 AS col1", &[&1i32])
373    ///     .await?
374    ///     .into_row()
375    ///     .await?
376    ///     .unwrap();
377    ///
378    /// assert_eq!(Some(1i32), row.get(0));
379    /// assert_eq!(Some(1i32), row.get("col1"));
380    /// # Ok(())
381    /// # }
382    /// ```
383    ///
384    /// # Panics
385    ///
386    /// - The requested type conversion (SQL->Rust) is not possible.
387    /// - The given index is out of bounds (column does not exist).
388    ///
389    /// Use [`try_get`] for a non-panicking version of the function.
390    ///
391    /// [`try_get`]: #method.try_get
392    #[track_caller]
393    pub fn get<'a, R, I>(&'a self, idx: I) -> Option<R>
394    where
395        R: FromSql<'a>,
396        I: QueryIdx,
397    {
398        self.try_get(idx).unwrap()
399    }
400
401    /// Retrieve a column's value for a given column index.
402    #[track_caller]
403    pub fn try_get<'a, R, I>(&'a self, idx: I) -> crate::Result<Option<R>>
404    where
405        R: FromSql<'a>,
406        I: QueryIdx,
407    {
408        let idx = idx.idx(self).ok_or_else(|| {
409            Error::Conversion(format!("Could not find column with index {}", idx).into())
410        })?;
411
412        let data = self.data.get(idx).unwrap();
413
414        R::from_sql(data)
415    }
416}
417
418impl IntoIterator for Row {
419    type Item = ColumnData<'static>;
420    type IntoIter = std::vec::IntoIter<Self::Item>;
421
422    fn into_iter(self) -> Self::IntoIter {
423        self.data.into_iter()
424    }
425}
426
427/// An extension trait for [`Row`] that provides test helpers.
428pub trait RowTestExt {
429    /// Create a new [`Row`] from the provided set of [`Column`]s and [`ColumnData`].
430    ///
431    /// Note: The [`Column`] and [`ColumnData`] pair are not checked, it's up to the caller to make
432    /// sure the pairing is valid.
433    fn build(columns: impl IntoIterator<Item = (Column, ColumnData<'static>)>) -> Self;
434
435    /// Sets the result index for this [`Row`].
436    fn with_result_index(&mut self, index: usize);
437}
438
439impl RowTestExt for Row {
440    fn build(values: impl IntoIterator<Item = (Column, ColumnData<'static>)>) -> Self {
441        let mut columns = Vec::default();
442        let mut token_row = TokenRow::new();
443
444        for (column, data) in values.into_iter() {
445            columns.push(column);
446            token_row.push(data);
447        }
448
449        Row {
450            columns: Arc::new(columns),
451            data: token_row,
452            result_index: 0,
453        }
454    }
455
456    fn with_result_index(&mut self, index: usize) {
457        self.result_index = index;
458    }
459}