Skip to main content

odbc_api/handles/
sql_char.rs

1//! The idea is to handle most of the conditional compilation around different SQL character types
2//! in this module, so the rest of the crate doesn't have to.
3
4// The rather akward expression:
5// `#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]` is used to
6// annotate things which should only compile if we use UTF-16 to communicate to the data source.
7// We use its negation:
8// `#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]` to
9// indicate a "narrow" charset for communicating with the datasource, which we assume to be UTF-8
10//
11// Currently I did not find a better way to use narrow function on non-windows platforms and wide
12// functions on windows platforms by default. I also want to enable explicitly overwriting the
13// default on both platforms. See also the documentation of the `narrow` and `wide` features in the
14// Cargo.toml manifest.
15
16use super::buffer::{buf_ptr, mut_buf_ptr};
17use std::{
18    borrow::Cow,
19    mem::{size_of, size_of_val},
20};
21
22#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
23use std::{ffi::CStr, string::FromUtf8Error};
24
25#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
26use std::{
27    char::{DecodeUtf16Error, decode_utf16},
28    marker::PhantomData,
29};
30
31#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
32use widestring::{U16CStr, Utf16String, iter::EncodeUtf16};
33
34#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
35pub type SqlChar = u8;
36#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
37pub type SqlChar = u16;
38
39#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
40pub type DecodingError = FromUtf8Error;
41#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
42pub type DecodingError = DecodeUtf16Error;
43
44#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
45pub fn slice_to_utf8(text: &[u8]) -> Result<String, FromUtf8Error> {
46    String::from_utf8(text.to_owned())
47}
48#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
49pub fn slice_to_utf8(text: &[u16]) -> Result<String, DecodeUtf16Error> {
50    decode_utf16(text.iter().copied()).collect()
51}
52
53#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
54pub fn slice_to_cow_utf8(text: &[u8]) -> Cow<'_, str> {
55    String::from_utf8_lossy(text)
56}
57#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
58pub fn slice_to_cow_utf8(text: &[u16]) -> Cow<'_, str> {
59    let text: Result<String, _> = decode_utf16(text.iter().copied()).collect();
60    text.unwrap().into()
61}
62
63#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
64fn sz_to_utf8(buffer: &[u16]) -> String {
65    let c_str = U16CStr::from_slice_truncate(buffer).unwrap();
66    c_str.to_string_lossy()
67}
68
69#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
70fn sz_to_utf8(buffer: &[u8]) -> String {
71    // Truncate slice at first zero.
72    let end = buffer
73        .iter()
74        .enumerate()
75        .find(|&(_index, &character)| character == b'\0')
76        .expect("Buffer must contain terminating zero.")
77        .0;
78    let c_str = unsafe { CStr::from_bytes_with_nul_unchecked(&buffer[..=end]) };
79    c_str.to_string_lossy().into_owned()
80}
81
82#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
83pub const WHITESPACE: SqlChar = 0x0020;
84
85#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
86pub const WHITESPACE: SqlChar = 0x20;
87
88/// Buffer length in bytes, not characters
89pub fn binary_length(buffer: &[SqlChar]) -> usize {
90    size_of_val(buffer)
91}
92
93/// `true` if the buffer has not been large enough to hold the entire string.
94///
95/// # Parameters
96///
97/// - `actuel_length_bin`: Actual length in bytes, but excluding the terminating zero.
98pub fn is_truncated_bin(buffer: &[SqlChar], actual_length_bin: usize) -> bool {
99    size_of_val(buffer) <= actual_length_bin
100}
101
102/// Resizes the underlying buffer to fit the size required to hold the entire string including
103/// terminating zero. Required length is provided in bytes (not characters), excluding the
104/// terminating zero.
105pub fn resize_to_fit_with_tz(buffer: &mut Vec<SqlChar>, required_binary_length: usize) {
106    // In order to use only minimal memory for drivers which stick to the ODBC standard we would
107    // use `+1` in the statement beneath. However it turns out the PostgreSQL driver will fill the
108    // last value with `0` instead of the last letter when used with a wide `SqlChar`. So we use
109    // `+2` to make it work with PostgreSql on windows, too.
110    buffer.resize((required_binary_length / size_of::<SqlChar>()) + 2, 0);
111}
112
113/// Resizes the underlying buffer to fit the size required to hold the entire string excluding
114/// terminating zero. Required length is provided in bytes (not characters), excluding the
115/// terminating zero.
116pub fn resize_to_fit_without_tz(buffer: &mut Vec<SqlChar>, required_binary_length: usize) {
117    buffer.resize(required_binary_length / size_of::<SqlChar>(), 0);
118}
119
120/// Handles conversion from UTF-8 string slices to ODBC SQL char encoding. Depending on the
121/// conditional compiliation due to feature flags, the UTF-8 strings are either passed without
122/// conversion to narrow method calls, or they are converted to UTF-16, before passed to the wide
123/// methods.
124pub struct SqlText<'a> {
125    /// In case we use wide methods we need to convert to UTF-16. We'll take ownership of the
126    /// buffer here.
127    #[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
128    text: Utf16String,
129    /// We include the lifetime in the declaration of the type still, so the borrow checker
130    /// complains, if we would mess up the compilation for narrow methods.
131    #[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
132    _ref: PhantomData<&'a str>,
133    /// In the case of narrow compiliation we just forward the string silce unchanged
134    #[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
135    text: &'a str,
136}
137
138impl<'a> SqlText<'a> {
139    #[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
140    /// Create an SqlText buffer from an UTF-8 string slice
141    pub fn new(text: &'a str) -> Self {
142        Self {
143            text: Utf16String::from_str(text),
144            _ref: PhantomData,
145        }
146    }
147    #[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
148    /// Create an SqlText buffer from an UTF-8 string slice
149    pub fn new(text: &'a str) -> Self {
150        Self { text }
151    }
152
153    #[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
154    pub fn ptr(&self) -> *const u16 {
155        buf_ptr(self.text.as_slice())
156    }
157    #[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
158    pub fn ptr(&self) -> *const u8 {
159        buf_ptr(self.text.as_bytes())
160    }
161
162    /// Length in characters
163    pub fn len_char(&self) -> usize {
164        self.text.len()
165    }
166}
167
168/// Use this buffer type to fetch zero terminated strings from the ODBC API. Either allocates a
169/// buffer for wide or narrow strings dependend on the features set.
170pub struct SzBuffer {
171    buffer: Vec<SqlChar>,
172}
173
174impl SzBuffer {
175    /// Creates a buffer which can hold at least `capacity` characters, excluding the terminating
176    /// zero. Or phrased differently. It will allocate one additional character to hold the
177    /// terminating zero, so the caller should not factor it into the size of capacity.
178    pub fn with_capacity(capacity: usize) -> Self {
179        Self {
180            // Allocate +1 character extra for terminating zero
181            buffer: vec![0; capacity + 1],
182        }
183    }
184
185    pub fn mut_buf(&mut self) -> &mut [SqlChar] {
186        // Use full capacity
187        self.buffer.resize(self.buffer.capacity(), 0);
188        &mut self.buffer
189    }
190
191    /// Create an owned utf-8 string from the internal buffer representation.
192    pub fn to_utf8(&self) -> String {
193        sz_to_utf8(&self.buffer)
194    }
195}
196
197/// We use this as an output buffer for strings. Allows for detecting truncation.
198pub struct OutputStringBuffer {
199    /// Buffer holding the string. Must also contains space for a terminating zero.
200    buffer: Vec<SqlChar>,
201    /// After the buffer has been filled, this should contain the actual length of the string. Can
202    /// be used to detect truncation.
203    actual_length: i16,
204}
205
206impl OutputStringBuffer {
207    /// Creates an empty string buffer. Useful if you want to e.g. use a prompt to complete the
208    /// connection string, but are not interested in the actual completed connection string.
209    pub fn empty() -> Self {
210        Self::with_buffer_size(0)
211    }
212
213    /// Creates a new instance of an output string buffer which can hold strings up to a size of
214    /// `max_str_len - 1` characters. `-1 because one place is needed for the terminating zero.
215    /// To hold a connection string the size should be at least 1024.
216    pub fn with_buffer_size(max_str_len: usize) -> Self {
217        Self {
218            buffer: vec![0; max_str_len],
219            actual_length: 0,
220        }
221    }
222
223    /// Ptr to the internal buffer. Used by ODBC API calls to fill the buffer.
224    pub fn mut_buf_ptr(&mut self) -> *mut SqlChar {
225        mut_buf_ptr(&mut self.buffer)
226    }
227
228    /// Length of the internal buffer in characters including the terminating zero.
229    pub fn buf_len(&self) -> i16 {
230        // Since buffer must always be able to hold at least one element, substracting `1` is always
231        // defined
232        self.buffer.len().try_into().unwrap()
233    }
234
235    /// Mutable pointer to actual output string length. Used by ODBC API calls to report truncation.
236    pub fn mut_actual_len_ptr(&mut self) -> *mut i16 {
237        &mut self.actual_length as *mut i16
238    }
239
240    /// Call this method to extract string from buffer after ODBC has filled it.
241    pub fn to_utf8(&self) -> String {
242        if self.buffer.is_empty() {
243            return String::new();
244        }
245
246        if self.is_truncated() {
247            // If the string is truncated we return the entire buffer excluding the terminating
248            // zero.
249            slice_to_utf8(&self.buffer[0..(self.buffer.len() - 1)]).unwrap()
250        } else {
251            // If the string is not truncated, we return not the entire buffer, but only the slice
252            // containing the actual string.
253            let actual_length: usize = self.actual_length.try_into().unwrap();
254            slice_to_utf8(&self.buffer[0..actual_length]).unwrap()
255        }
256    }
257
258    /// True if the buffer had not been large enough to hold the string.
259    pub fn is_truncated(&self) -> bool {
260        self.actual_length >= self.buffer.len().try_into().unwrap()
261    }
262}