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}