Skip to main content

odbc_sys/
connection_attribute.rs

1use std::{
2    alloc::{alloc, handle_alloc_error, Layout},
3    ptr,
4};
5
6/// Connection attributes for `SQLSetConnectAttr`
7///
8/// See: <https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
9#[repr(transparent)]
10#[derive(Debug, PartialEq, Eq, Clone, Copy)]
11pub struct ConnectionAttribute(pub i32);
12
13impl ConnectionAttribute {
14    pub const ASYNC_ENABLE: Self = Self(4);
15
16    /// Equivalent to `SQL_ATTR_ACCESS_MODE` in the C-API
17    pub const ACCESS_MODE: Self = Self(101);
18    /// Equivalent to `SQL_ATTR_AUTOCOMMIT` in the C-API
19    pub const AUTOCOMMIT: Self = Self(102);
20    /// Equivalent to `SQL_ATTR_LOGIN_TIMEOUT` in the C-API
21    pub const LOGIN_TIMEOUT: Self = Self(103);
22    /// Equivalent to `SQL_ATTR_TRACE` in the C-API
23    pub const TRACE: Self = Self(104);
24    /// Equivalent to `SQL_ATTR_TRACEFILE` in the C-API
25    pub const TRACEFILE: Self = Self(105);
26    /// Equivalent to `SQL_ATTR_TRANSLATE_LIB` in the C-API
27    pub const TRANSLATE_LIB: Self = Self(106);
28    /// Equivalent to `SQL_ATTR_TRANSLATE_OPTION` in the C-API
29    pub const TRANSLATE_OPTION: Self = Self(107);
30    /// Equivalent to `SQL_ATTR_TXN_ISOLATION` in the C-API
31    pub const TXN_ISOLATION: Self = Self(108);
32    /// Equivalent to `SQL_ATTR_CURRENT_CATALOG` in the C-API
33    pub const CURRENT_CATALOG: Self = Self(109);
34    /// Equivalent to `SQL_ATTR_ODBC_CURSORS` in the C-API
35    pub const ODBC_CURSORS: Self = Self(110);
36    /// Equivalent to `SQL_ATTR_QUIET_MODE` in the C-API
37    pub const QUIET_MODE: Self = Self(111);
38    /// Equivalent to `SQL_ATTR_PACKET_SIZE` in the C-API
39    pub const PACKET_SIZE: Self = Self(112);
40    /// Equivalent to `SQL_ATTR_CONNECTION_TIMEOUT` in the C-API
41    pub const CONNECTION_TIMEOUT: Self = Self(113);
42    /// Equivalent to `SQL_ATTR_DISCONNECT_BEHAVIOUR` in the C-API
43    pub const DISCONNECT_BEHAVIOUR: Self = Self(114);
44
45    // Attribute 115 is `SQL_ATTR_ANSI_APP`. This attribute is not set by an odbc applictation, but
46    // by the driver manager itself. Therfore it is not included here.
47
48    /// Equivalent to `SQL_ATTR_DBC_INFO_TOKEN`
49    ///
50    /// Reset the pooled connection in case it is not a perfect match
51    pub const DBC_INFO_TOKEN: Self = Self(118);
52
53    /// Equivalent to `SQL_ATTR_RESET_CONNECTION` in the C-API
54    pub const RESET_CONNECTION: Self = Self(116);
55    /// Equivalent to `SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE` in the C-API
56    pub const ASYNC_DBC_FUNCTIONS_ENABLE: Self = Self(117);
57    /// Equivalent to `SQL_ATTR_ASYNC_DBC_EVENT` in the C-API
58    pub const ASYNC_DBC_EVENT: Self = Self(119);
59    /// Equivalent to `SQL_ATTR_ENLIST_IN_DTC` in the C-API
60    pub const ENLIST_IN_DTC: Self = Self(1207);
61    /// Equivalent to `SQL_ATTR_ENLIST_IN_XA` in the C-API
62    pub const ENLIST_IN_XA: Self = Self(1208);
63    pub const CONNECTION_DEAD: Self = Self(1209);
64    pub const AUTO_IPD: Self = Self(10001);
65    pub const METADATA_ID: Self = Self(10014);
66
67    /// Equivalent to `SQL_COPT_SS_ACCESS_TOKEN` in `msodbcsql.h`.
68    ///
69    /// Pre-connect attribute to authenticate with a Microsoft Entra ID access token. Specific to
70    /// the Microsoft ODBC Driver for SQL Server. Setting this for other drivers could cause
71    /// undefined behavior. The value is a pointer to an [`SsAccessToken`]. It must stay allocated
72    /// for as long as the connection handle is allocated.
73    ///
74    /// See:
75    /// <https://learn.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token>
76    pub const SS_ACCESS_TOKEN: Self = Self(1256);
77}
78
79/// Equivalent to `ACCESSTOKEN` in `msodbcsql.h`. Value of [`ConnectionAttribute::SS_ACCESS_TOKEN`].
80///
81/// Specific to the Microsoft ODBC Driver for SQL Server. A dynamically sized type: pointers to it
82/// are fat and carry the length of `data`, which must equal `data_size`. Cast to a thin pointer
83/// before passing it to the driver.
84///
85/// See:
86/// <https://learn.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token>
87#[repr(C)]
88pub struct SsAccessToken {
89    /// Length of `data` in bytes. Does not include a terminating zero.
90    pub data_size: u32,
91    /// Flexible array member (`BYTE data[]` in C). Directly follows `data_size`.
92    ///
93    /// According to Microsoft, a token obtained from an OAuth 2.0 JSON response must be expanded,
94    /// so each byte is followed by a zero byte. For the ASCII characters of an access token, this
95    /// equals UTF-16 with little endian byte order. This holds whether the narrow or wide ODBC
96    /// functions are used, since the driver manager passes the pointer through unchanged.
97    pub data: [u8],
98}
99
100impl SsAccessToken {
101    /// `data` must be the access token in the format expected by the driver: each byte of the
102    /// ASCII token followed by a zero byte. Use [`Self::from_text`] to pass the token text instead.
103    ///
104    /// # Panics
105    ///
106    /// If `data` is larger than `u32::MAX` bytes.
107    pub fn new(data: &[u8]) -> Box<Self> {
108        let data_size =
109            u32::try_from(data.len()).expect("Access token must not be larger than u32::MAX bytes");
110        // Can not fail, since `data.len()` fits into an `u32`.
111        let (layout, data_offset) = Layout::new::<u32>()
112            .extend(Layout::array::<u8>(data.len()).unwrap())
113            .unwrap();
114        let layout = layout.pad_to_align();
115        // Safety: `layout` has a non-zero size, since it contains at least the `u32` header. Header
116        // and data are written within the allocation, at the offsets computed by `Layout::extend`.
117        // The fat pointer carries `data.len()` as length of `data`, so `Layout::for_value` of the
118        // resulting box equals `layout`, as required by `Box::from_raw`.
119        unsafe {
120            let raw = alloc(layout);
121            if raw.is_null() {
122                handle_alloc_error(layout);
123            }
124            raw.cast::<u32>().write(data_size);
125            ptr::copy_nonoverlapping(data.as_ptr(), raw.add(data_offset), data.len());
126            Box::from_raw(ptr::slice_from_raw_parts_mut(raw, data.len()) as *mut SsAccessToken)
127        }
128    }
129
130    /// `token` is the access token as text, e.g. obtained from an OAuth 2.0 JSON response.
131    ///
132    /// # Panics
133    ///
134    /// If the encoded token is larger than `u32::MAX` bytes.
135    pub fn from_text(token: &str) -> Box<Self> {
136        let data: Vec<u8> = token.encode_utf16().flat_map(u16::to_le_bytes).collect();
137        Self::new(&data)
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use std::mem::{align_of_val, size_of_val};
144
145    use super::SsAccessToken;
146
147    #[test]
148    fn access_token_from_str_is_encoded_as_utf16_little_endian() {
149        let token = SsAccessToken::from_text("eyJ");
150
151        assert_eq!(6, token.data_size);
152        assert_eq!(&[b'e', 0, b'y', 0, b'J', 0], &token.data);
153    }
154
155    #[test]
156    fn access_token_new_copies_bytes_verbatim() {
157        let token = SsAccessToken::new(&[1, 2, 3]);
158
159        assert_eq!(3, token.data_size);
160        assert_eq!(&[1, 2, 3], &token.data);
161    }
162
163    #[test]
164    fn access_token_has_c_layout() {
165        let token = SsAccessToken::new(&[1, 2, 3]);
166
167        let start = &*token as *const SsAccessToken as *const u8 as usize;
168        assert_eq!(4, token.data.as_ptr() as usize - start);
169        // 4 bytes header, 3 bytes data, padded to alignment of 4
170        assert_eq!(8, size_of_val(&*token));
171        assert_eq!(4, align_of_val(&*token));
172    }
173}