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}