odbc_api/
result_set_metadata.rs

1use std::num::NonZeroUsize;
2
3use odbc_sys::SqlDataType;
4
5use crate::{
6    ColumnDescription, DataType, Error, Nullability,
7    handles::{AsStatementRef, SqlChar, Statement, slice_to_utf8},
8};
9
10/// Provides Metadata of the resulting the result set. Implemented by `Cursor` types and prepared
11/// queries. Fetching metadata from a prepared query might be expensive (driver dependent), so your
12/// application should fetch the Metadata it requires from the `Cursor` if possible.
13///
14/// See also:
15/// <https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/result-set-metadata>
16pub trait ResultSetMetadata: AsStatementRef {
17    /// Fetch a column description using the column index.
18    ///
19    /// # Parameters
20    ///
21    /// * `column_number`: Column index. `0` is the bookmark column. The other column indices start
22    ///   with `1`.
23    /// * `column_description`: Holds the description of the column after the call. This method does
24    ///   not provide strong exception safety as the value of this argument is undefined in case of
25    ///   an error.
26    fn describe_col(
27        &mut self,
28        column_number: u16,
29        column_description: &mut ColumnDescription,
30    ) -> Result<(), Error> {
31        let stmt = self.as_stmt_ref();
32        stmt.describe_col(column_number, column_description)
33            .into_result(&stmt)
34    }
35
36    /// Number of columns in result set. Can also be used to see whether executing a prepared
37    /// Statement ([`crate::Prepared`]) would yield a result set, as this would return `0` if it
38    /// does not.
39    ///
40    /// See also:
41    /// <https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlnumresultcols-function>
42    fn num_result_cols(&mut self) -> Result<i16, Error> {
43        let stmt = self.as_stmt_ref();
44        stmt.num_result_cols().into_result(&stmt)
45    }
46
47    /// `true` if a given column in a result set is unsigned or not a numeric type, `false`
48    /// otherwise.
49    ///
50    /// `column_number`: Index of the column, starting at 1.
51    fn column_is_unsigned(&mut self, column_number: u16) -> Result<bool, Error> {
52        let stmt = self.as_stmt_ref();
53        stmt.is_unsigned_column(column_number).into_result(&stmt)
54    }
55
56    /// Size in bytes of the columns. For variable sized types this is the maximum size, excluding a
57    /// terminating zero.
58    ///
59    /// `column_number`: Index of the column, starting at 1.
60    fn col_octet_length(&mut self, column_number: u16) -> Result<Option<NonZeroUsize>, Error> {
61        let stmt = self.as_stmt_ref();
62        stmt.col_octet_length(column_number)
63            .into_result(&stmt)
64            .map(|signed| NonZeroUsize::new(signed.max(0) as usize))
65    }
66
67    /// Maximum number of characters required to display data from the column. If the driver is
68    /// unable to provide a maximum `None` is returned.
69    ///
70    /// `column_number`: Index of the column, starting at 1.
71    fn col_display_size(&mut self, column_number: u16) -> Result<Option<NonZeroUsize>, Error> {
72        let stmt = self.as_stmt_ref();
73        stmt.col_display_size(column_number)
74            .into_result(&stmt)
75            // Map negative values to `0`. `0` is used by MSSQL to indicate a missing upper bound
76            // `-4` (`NO_TOTAL`) is used by MySQL to do the same. Mapping them both to the same
77            // value allows for less error prone generic applications. Making this value `None`
78            // instead of zero makes it explicit, that an upper bound can not always be known. It
79            // also prevents the order from being misunderstood, because the largest possible value
80            // is obviously `> 0` in this case, yet `0` is smaller than any other value.
81            .map(|signed| NonZeroUsize::new(signed.max(0) as usize))
82    }
83
84    /// Precision of the column.
85    ///
86    /// Denotes the applicable precision. For data types SQL_TYPE_TIME, SQL_TYPE_TIMESTAMP, and all
87    /// the interval data types that represent a time interval, its value is the applicable
88    /// precision of the fractional seconds component.
89    fn col_precision(&mut self, column_number: u16) -> Result<isize, Error> {
90        let stmt = self.as_stmt_ref();
91        stmt.col_precision(column_number).into_result(&stmt)
92    }
93
94    /// The applicable scale for a numeric data type. For DECIMAL and NUMERIC data types, this is
95    /// the defined scale. It is undefined for all other data types.
96    fn col_scale(&mut self, column_number: u16) -> Result<isize, Error> {
97        let stmt = self.as_stmt_ref();
98        stmt.col_scale(column_number).into_result(&stmt)
99    }
100
101    /// Nullability of the column.
102    ///
103    /// `column_number`: Index of the column, starting at 1.
104    ///
105    /// See `SQL_DESC_NULLABLE ` in the ODBC reference:
106    /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlcolattribute-function>
107    fn col_nullability(&mut self, column_number: u16) -> Result<Nullability, Error> {
108        let stmt = self.as_stmt_ref();
109        stmt.col_nullability(column_number).into_result(&stmt)
110    }
111
112    /// The column alias, if it applies. If the column alias does not apply, the column name is
113    /// returned. If there is no column name or a column alias, an empty string is returned.
114    fn col_name(&mut self, column_number: u16) -> Result<String, Error> {
115        let stmt = self.as_stmt_ref();
116        let mut buf = vec![0; 1024];
117        stmt.col_name(column_number, &mut buf).into_result(&stmt)?;
118        Ok(slice_to_utf8(&buf).unwrap())
119    }
120
121    /// Use this if you want to iterate over all column names and allocate a `String` for each one.
122    ///
123    /// This is a wrapper around `col_name` introduced for convenience.
124    fn column_names(&mut self) -> Result<ColumnNamesIt<'_, Self>, Error> {
125        ColumnNamesIt::new(self)
126    }
127
128    /// Data type of the specified column.
129    ///
130    /// `column_number`: Index of the column, starting at 1.
131    fn col_data_type(&mut self, column_number: u16) -> Result<DataType, Error> {
132        // There is some repetition of knowledge here, about how SqlDataType maps to DataType.
133        // Maybe we can unify this with [`DataType::new`].
134
135        let stmt = self.as_stmt_ref();
136        let kind = stmt.col_concise_type(column_number).into_result(&stmt)?;
137        let dt = match kind {
138            SqlDataType::UNKNOWN_TYPE => DataType::Unknown,
139            SqlDataType::EXT_VAR_BINARY => DataType::Varbinary {
140                length: self.col_octet_length(column_number)?,
141            },
142            SqlDataType::EXT_LONG_VAR_BINARY => DataType::LongVarbinary {
143                length: self.col_octet_length(column_number)?,
144            },
145            SqlDataType::EXT_BINARY => DataType::Binary {
146                length: self.col_octet_length(column_number)?,
147            },
148            SqlDataType::EXT_W_VARCHAR => DataType::WVarchar {
149                length: self.col_display_size(column_number)?,
150            },
151            SqlDataType::EXT_W_CHAR => DataType::WChar {
152                length: self.col_display_size(column_number)?,
153            },
154            SqlDataType::EXT_LONG_VARCHAR => DataType::LongVarchar {
155                length: self.col_display_size(column_number)?,
156            },
157            SqlDataType::EXT_W_LONG_VARCHAR => DataType::WLongVarchar {
158                length: self.col_display_size(column_number)?,
159            },
160            SqlDataType::CHAR => DataType::Char {
161                length: self.col_display_size(column_number)?,
162            },
163            SqlDataType::VARCHAR => DataType::Varchar {
164                length: self.col_display_size(column_number)?,
165            },
166            SqlDataType::NUMERIC => DataType::Numeric {
167                precision: self.col_precision(column_number)?.try_into().unwrap(),
168                scale: self.col_scale(column_number)?.try_into().unwrap(),
169            },
170            SqlDataType::DECIMAL => DataType::Decimal {
171                precision: self.col_precision(column_number)?.try_into().unwrap(),
172                scale: self.col_scale(column_number)?.try_into().unwrap(),
173            },
174            SqlDataType::INTEGER => DataType::Integer,
175            SqlDataType::SMALLINT => DataType::SmallInt,
176            SqlDataType::FLOAT => DataType::Float {
177                precision: self.col_precision(column_number)?.try_into().unwrap(),
178            },
179            SqlDataType::REAL => DataType::Real,
180            SqlDataType::DOUBLE => DataType::Double,
181            SqlDataType::DATE => DataType::Date,
182            SqlDataType::TIME => DataType::Time {
183                precision: self.col_precision(column_number)?.try_into().unwrap(),
184            },
185            SqlDataType::TIMESTAMP => DataType::Timestamp {
186                precision: self.col_precision(column_number)?.try_into().unwrap(),
187            },
188            SqlDataType::EXT_BIG_INT => DataType::BigInt,
189            SqlDataType::EXT_TINY_INT => DataType::TinyInt,
190            SqlDataType::EXT_BIT => DataType::Bit,
191            other => {
192                let mut column_description = ColumnDescription::default();
193                self.describe_col(column_number, &mut column_description)?;
194                DataType::Other {
195                    data_type: other,
196                    column_size: column_description.data_type.column_size(),
197                    decimal_digits: column_description.data_type.decimal_digits(),
198                }
199            }
200        };
201        Ok(dt)
202    }
203}
204
205/// Buffer sizes able to hold the display size of each column in utf-8 encoding. You may call this
206/// method to figure out suitable buffer sizes for text columns. [`buffers::TextRowSet::for_cursor`]
207/// will invoke this function for you.
208///
209/// # Parameters
210///
211/// * `metadata`: Used to query the display size for each column of the row set. For character
212///   data the length in characters is multiplied by 4 in order to have enough space for 4 byte
213///   utf-8 characters. This is a pessimization for some data sources (e.g. SQLite 3) which do
214///   interpret the size of a `VARCHAR(5)` column as 5 bytes rather than 5 characters.
215pub fn utf8_display_sizes(
216    metadata: &mut impl ResultSetMetadata,
217) -> Result<impl Iterator<Item = Result<Option<NonZeroUsize>, Error>> + '_, Error> {
218    let num_cols: u16 = metadata.num_result_cols()?.try_into().unwrap();
219    let it = (1..(num_cols + 1)).map(move |col_index| {
220        // Ask driver for buffer length
221        let max_str_len = if let Some(encoded_len) = metadata.col_data_type(col_index)?.utf8_len() {
222            Some(encoded_len)
223        } else {
224            metadata.col_display_size(col_index)?
225        };
226        Ok(max_str_len)
227    });
228    Ok(it)
229}
230
231/// An iterator calling `col_name` for each column_name and converting the result into UTF-8. See
232/// [`ResultSetMetada::column_names`].
233pub struct ColumnNamesIt<'c, C: ?Sized> {
234    cursor: &'c mut C,
235    buffer: Vec<SqlChar>,
236    column: u16,
237    num_cols: u16,
238}
239
240impl<'c, C: ResultSetMetadata + ?Sized> ColumnNamesIt<'c, C> {
241    fn new(cursor: &'c mut C) -> Result<Self, Error> {
242        let num_cols = cursor.num_result_cols()?.try_into().unwrap();
243        Ok(Self {
244            cursor,
245            // Some ODBC drivers do not report the required size to hold the column name. Starting
246            // with a reasonable sized buffers, allows us to fetch reasonable sized column alias
247            // even from those.
248            buffer: Vec::with_capacity(128),
249            num_cols,
250            column: 1,
251        })
252    }
253}
254
255impl<C> Iterator for ColumnNamesIt<'_, C>
256where
257    C: ResultSetMetadata,
258{
259    type Item = Result<String, Error>;
260
261    fn next(&mut self) -> Option<Self::Item> {
262        if self.column <= self.num_cols {
263            // stmt instead of cursor.col_name, so we can efficently reuse the buffer and avoid
264            // extra allocations.
265            let stmt = self.cursor.as_stmt_ref();
266
267            let result = stmt
268                .col_name(self.column, &mut self.buffer)
269                .into_result(&stmt)
270                .map(|()| slice_to_utf8(&self.buffer).unwrap());
271            self.column += 1;
272            Some(result)
273        } else {
274            None
275        }
276    }
277
278    fn size_hint(&self) -> (usize, Option<usize>) {
279        let num_cols = self.num_cols as usize;
280        (num_cols, Some(num_cols))
281    }
282}
283
284impl<C> ExactSizeIterator for ColumnNamesIt<'_, C> where C: ResultSetMetadata {}