Skip to main content

odbc_api/catalog/
columns.rs

1use crate::{
2    CursorImpl, Error, Nullable, TruncationInfo,
3    buffers::{FetchRow, FetchRowMember as _},
4    handles::{AsStatementRef, SqlText, Statement, StatementRef},
5    parameter::VarCharArray,
6};
7
8/// A row returned by [`crate::Preallocated::columns`]. The members are associated with the
9/// columns of the result set returned by [`crate::Preallocated::columns_cursor`].
10///
11/// Sensible defaults for the buffer sizes for the variable-length columns have been chosen.
12/// However, your driver could return larger strings or be guaranteed to return shoter ones. Your
13/// ODBC driver may also choose to add additional columns at the end of the result set. If you
14/// want to change the buffer sizes or fetch the additional driver specific data, use [
15/// [`crate::Preallocated::columns_cursor`] directly and manually bind a columnar buffer to it.
16///
17/// See: <https://learn.microsoft.com/sql/odbc/reference/syntax/sqlcolumns-function>
18#[derive(Clone, Copy, Default)]
19pub struct ColumnsRow {
20    /// Binds to the `TABLE_CAT` column. Catalog name of the table. NULL if not applicable.
21    pub catalog: VarCharArray<128>,
22    /// Binds to the `TABLE_SCHEM` column. Schema name of the table. NULL if not applicable.
23    pub schema: VarCharArray<128>,
24    /// Binds to the `TABLE_NAME` column. Table name.
25    pub table: VarCharArray<255>,
26    /// Binds to the `COLUMN_NAME` column. Column name. Empty string for unnamed columns.
27    pub column_name: VarCharArray<255>,
28    /// Binds to the `DATA_TYPE` column. SQL data type.
29    pub data_type: i16,
30    /// Binds to the `TYPE_NAME` column. Data source-dependent data type name.
31    pub type_name: VarCharArray<256>,
32    /// Binds to the `COLUMN_SIZE` column. Size of the column in the data source. NULL if not
33    /// applicable.
34    pub column_size: Nullable<i32>,
35    /// Binds to the `BUFFER_LENGTH` column. Length in bytes of data transferred on fetch.
36    pub buffer_length: Nullable<i32>,
37    /// Binds to the `DECIMAL_DIGITS` column. Decimal digits of the column. NULL if not applicable.
38    pub decimal_digits: Nullable<i16>,
39    /// Binds to the `NUM_PREC_RADIX` column. Either 10, 2, or NULL.
40    pub num_prec_radix: Nullable<i16>,
41    /// Binds to the `NULLABLE` column. Whether the column accepts NULLs.
42    pub nullable: i16,
43    /// Binds to the `REMARKS` column. Description of the column. NULL if unavailable.
44    pub remarks: VarCharArray<1024>,
45    /// Binds to the `COLUMN_DEF` column. Default value of the column. NULL if unspecified.
46    pub column_default: VarCharArray<4000>,
47    /// Binds to the `SQL_DATA_TYPE` column. SQL data type as it appears in the `SQL_DESC_TYPE`
48    /// record field in the IRD.
49    pub sql_data_type: i16,
50    /// Binds to the `SQL_DATETIME_SUB` column. Subtype code for datetime and interval data types.
51    /// NULL for other data types.
52    pub sql_datetime_sub: Nullable<i16>,
53    /// Binds to the `CHAR_OCTET_LENGTH` column. Maximum length in bytes of a character or binary
54    /// column. NULL for other data types.
55    pub char_octet_length: Nullable<i32>,
56    /// Binds to the `ORDINAL_POSITION` column. Ordinal position of the column in the table
57    /// (starting with 1).
58    pub ordinal_position: i32,
59    /// Binds to the `IS_NULLABLE` column. `"NO"` if the column is known to be not nullable.
60    /// `"YES"` if the column might be nullable. Empty string if nullability is unknown.
61    pub is_nullable: VarCharArray<4>,
62}
63
64unsafe impl FetchRow for ColumnsRow {
65    unsafe fn bind_columns_to_cursor(&mut self, mut cursor: StatementRef<'_>) -> Result<(), Error> {
66        unsafe {
67            self.catalog.bind_to_col(1, &mut cursor)?;
68            self.schema.bind_to_col(2, &mut cursor)?;
69            self.table.bind_to_col(3, &mut cursor)?;
70            self.column_name.bind_to_col(4, &mut cursor)?;
71            self.data_type.bind_to_col(5, &mut cursor)?;
72            self.type_name.bind_to_col(6, &mut cursor)?;
73            self.column_size.bind_to_col(7, &mut cursor)?;
74            self.buffer_length.bind_to_col(8, &mut cursor)?;
75            self.decimal_digits.bind_to_col(9, &mut cursor)?;
76            self.num_prec_radix.bind_to_col(10, &mut cursor)?;
77            self.nullable.bind_to_col(11, &mut cursor)?;
78            self.remarks.bind_to_col(12, &mut cursor)?;
79            self.column_default.bind_to_col(13, &mut cursor)?;
80            self.sql_data_type.bind_to_col(14, &mut cursor)?;
81            self.sql_datetime_sub.bind_to_col(15, &mut cursor)?;
82            self.char_octet_length.bind_to_col(16, &mut cursor)?;
83            self.ordinal_position.bind_to_col(17, &mut cursor)?;
84            self.is_nullable.bind_to_col(18, &mut cursor)?;
85            Ok(())
86        }
87    }
88
89    fn find_truncation(&self) -> Option<TruncationInfo> {
90        if let Some(t) = self.catalog.find_truncation(0) {
91            return Some(t);
92        }
93        if let Some(t) = self.schema.find_truncation(1) {
94            return Some(t);
95        }
96        if let Some(t) = self.table.find_truncation(2) {
97            return Some(t);
98        }
99        if let Some(t) = self.column_name.find_truncation(3) {
100            return Some(t);
101        }
102        if let Some(t) = self.data_type.find_truncation(4) {
103            return Some(t);
104        }
105        if let Some(t) = self.type_name.find_truncation(5) {
106            return Some(t);
107        }
108        if let Some(t) = self.column_size.find_truncation(6) {
109            return Some(t);
110        }
111        if let Some(t) = self.buffer_length.find_truncation(7) {
112            return Some(t);
113        }
114        if let Some(t) = self.decimal_digits.find_truncation(8) {
115            return Some(t);
116        }
117        if let Some(t) = self.num_prec_radix.find_truncation(9) {
118            return Some(t);
119        }
120        if let Some(t) = self.nullable.find_truncation(10) {
121            return Some(t);
122        }
123        if let Some(t) = self.remarks.find_truncation(11) {
124            return Some(t);
125        }
126        if let Some(t) = self.column_default.find_truncation(12) {
127            return Some(t);
128        }
129        if let Some(t) = self.sql_data_type.find_truncation(13) {
130            return Some(t);
131        }
132        if let Some(t) = self.sql_datetime_sub.find_truncation(14) {
133            return Some(t);
134        }
135        if let Some(t) = self.char_octet_length.find_truncation(15) {
136            return Some(t);
137        }
138        if let Some(t) = self.ordinal_position.find_truncation(16) {
139            return Some(t);
140        }
141        if let Some(t) = self.is_nullable.find_truncation(17) {
142            return Some(t);
143        }
144        None
145    }
146}
147
148pub fn execute_columns<S>(
149    mut statement: S,
150    catalog_name: &SqlText,
151    schema_name: &SqlText,
152    table_name: &SqlText,
153    column_name: &SqlText,
154) -> Result<CursorImpl<S>, Error>
155where
156    S: AsStatementRef,
157{
158    let mut stmt = statement.as_stmt_ref();
159
160    stmt.columns(catalog_name, schema_name, table_name, column_name)
161        .into_result(&stmt)?;
162
163    // We assume columns always creates a result set, since it works like a SELECT statement.
164    debug_assert_ne!(stmt.num_result_cols().unwrap(), 0);
165
166    // Safe: `statement` is in cursor state
167    let cursor = unsafe { CursorImpl::new(statement) };
168    Ok(cursor)
169}