Skip to content

Commit

Permalink
feat!: Introduce wide feature flag. Use narrow function calls by defa…
Browse files Browse the repository at this point in the history
…ult on non-windows platforms.
  • Loading branch information
pacman82 committed Nov 24, 2024
1 parent c329a84 commit 3e6b329
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 51 deletions.
25 changes: 19 additions & 6 deletions odbc-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,35 @@ readme = "../Readme.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
# Experimental feature to enabling narrow function calls.
# Use narrow function calls.
#
# Many functions which accept string arguments in the ODBC C API come in two different flavours. For
# example `SQLConnect` and `SQLConnectW`. The former are called narrow function calls and the latter
# are called wide. They differ in the type they used to encode characters (`u8` vs `u16`). Sadly
# narrow may not always be assumed to be UTF-8 as it is dependend on the system locale which is
# usually not UTF-8 on windows system. The wide function calls could be relied upon to always be
# UTF-16 on any platform, but do not seem to work well with iodbc.
# UTF-16 on any platform, but do not seem to work well with iodbc and are less battle tested with
# Linux versions of ODBC drivers.
#
# Currently this library uses wide function call exclusively. This feature can be enabled to cause
# compliation against narrow functions, but it is not fully implemented yet.
# Currently this library uses wide function call on windows systems by default. On non windows
# systems however narrow function calls are used. Enabling this feature will cause the use of
# the narrow function calls even on windows system. One reason to do so, would be e.g. you know your
# program is running on a windows system with an UTF-8 locale, and trust the driver to handle UTF-8
# appropriatly. If both `narrow` and `wide` work, `narrow` usually is faster and needs less encoding
# and decoding between string representations.
#
# Note that this is the encoding used for statement text and other string arguments, not for the
# payload of VARCHAR columns, or other column types in the result set.
# Note that this is the encoding used for statement text and other string arguments, yet not for the
# payload of VARCHAR columns, or other column types in the result set. This can be choosen at
# runtime by the application.
narrow=[]

# Use wide function calls.
#
# See the documentation of `narrow` feature above for more information. Enabling this feature will
# trigger the use of `wide` function calls, even on "non-windows" platforms. If both `narrow` and
# `wide` are enabled, `wide` takes precedence.
wide=[]

# `odbc-api` uses ODBC 3.80 by default, which is well supported both in windows and on linux through
# `UnixODBC`. Yet iodbc, for now does only support ODBC 3.5, so you can set this flag in order to
# include only symbols available in ODBC 3.5 and create an environment which declares the ODBC
Expand Down
4 changes: 2 additions & 2 deletions odbc-api/src/handles/column_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ impl ColumnDescription {
/// [`ColumnDescription`]. This constructor enables you to do that, without caring which type
/// `SqlChar` resolves to.
pub fn new(name: &str, data_type: DataType, nullability: Nullability) -> Self {
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
pub fn utf8_to_vec_char(text: &str) -> Vec<u8> {
text.to_owned().into_bytes()
}
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
pub fn utf8_to_vec_char(text: &str) -> Vec<u16> {
use widestring::U16String;
U16String::from_str(text).into_vec()
Expand Down
4 changes: 2 additions & 2 deletions odbc-api/src/handles/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ use odbc_sys::{
};
use std::{ffi::c_void, marker::PhantomData, mem::size_of, ptr::null_mut};

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
use odbc_sys::{
SQLConnect as sql_connect, SQLDriverConnect as sql_driver_connect,
SQLGetConnectAttr as sql_get_connect_attr, SQLGetInfo as sql_get_info,
SQLSetConnectAttr as sql_set_connect_attr,
};

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
use odbc_sys::{
SQLConnectW as sql_connect, SQLDriverConnectW as sql_driver_connect,
SQLGetConnectAttrW as sql_get_connect_attr, SQLGetInfoW as sql_get_info,
Expand Down
4 changes: 2 additions & 2 deletions odbc-api/src/handles/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use odbc_sys::{

use super::{sql_result::ExtSqlReturn, AsHandle, SqlResult};

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
use odbc_sys::SQLSetDescField as sql_set_desc_field;

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
use odbc_sys::SQLSetDescFieldW as sql_set_desc_field;

/// A descriptor associated with a statement. This wrapper does not wrap explicitly allocated
Expand Down
8 changes: 4 additions & 4 deletions odbc-api/src/handles/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ use std::fmt;

// Starting with odbc 5 we may be able to specify utf8 encoding. Until then, we may need to fall
// back on the 'W' wide function calls.
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
use odbc_sys::SQLGetDiagRecW as sql_get_diag_rec;

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
use odbc_sys::SQLGetDiagRec as sql_get_diag_rec;

/// A buffer large enough to hold an `SOLState` for diagnostics
Expand Down Expand Up @@ -280,12 +280,12 @@ mod tests {

use super::Record;

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
fn to_vec_sql_char(text: &str) -> Vec<u16> {
text.encode_utf16().collect()
}

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
fn to_vec_sql_char(text: &str) -> Vec<u8> {
text.bytes().collect()
}
Expand Down
4 changes: 2 additions & 2 deletions odbc-api/src/handles/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ use odbc_sys::{
};
use std::ptr::null_mut;

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
use odbc_sys::{SQLDataSources as sql_data_sources, SQLDrivers as sql_drivers};

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
use odbc_sys::{SQLDataSourcesW as sql_data_sources, SQLDriversW as sql_drivers};

/// An `Environment` is a global context, in which to access data.
Expand Down
52 changes: 32 additions & 20 deletions odbc-api/src/handles/sql_char.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,71 @@
//! The idea is to handle most of the conditional compilation around different SQL character types
//! in this module, so the rest of the crate doesn't have to.
// The rather akward expression:
// `#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]` is used to
// annotate things which should only compile if we use UTF-16 to communicate to the data source.
// We use its negation:
// `#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]` to
// indicate a "narrow" charset for communicating with the datasource, which we assume to be UTF-8
//
// Currently I did not find a better way to use narrow function on non-windows platforms and wide
// functions on windows platforms by default. I also want to enable explicitly overwriting the
// default on both platforms. See also the documentation of the `narrow` and `wide` features in the
// Cargo.toml manifest.

use super::buffer::{buf_ptr, mut_buf_ptr};
use std::{
borrow::Cow,
mem::{size_of, size_of_val},
};

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
use std::{ffi::CStr, string::FromUtf8Error};

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
use std::{
char::{decode_utf16, DecodeUtf16Error},
marker::PhantomData,
};

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
use widestring::{U16CStr, U16String};

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
pub type SqlChar = u8;
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
pub type SqlChar = u16;

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
pub type DecodingError = FromUtf8Error;
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
pub type DecodingError = DecodeUtf16Error;

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
pub fn slice_to_utf8(text: &[u8]) -> Result<String, FromUtf8Error> {
String::from_utf8(text.to_owned())
}
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
pub fn slice_to_utf8(text: &[u16]) -> Result<String, DecodeUtf16Error> {
decode_utf16(text.iter().copied()).collect()
}

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
pub fn slice_to_cow_utf8(text: &[u8]) -> Cow<str> {
String::from_utf8_lossy(text)
}
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
pub fn slice_to_cow_utf8(text: &[u16]) -> Cow<str> {
let text: Result<String, _> = decode_utf16(text.iter().copied()).collect();
text.unwrap().into()
}

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
fn sz_to_utf8(buffer: &[u16]) -> String {
let c_str = U16CStr::from_slice_truncate(buffer).unwrap();
c_str.to_string_lossy()
}
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
fn sz_to_utf8(buffer: &[u8]) -> String {
// Truncate slice at first zero.
let end = buffer
Expand Down Expand Up @@ -105,37 +117,37 @@ pub fn resize_to_fit_without_tz(buffer: &mut Vec<SqlChar>, required_binary_lengt
pub struct SqlText<'a> {
/// In case we use wide methods we need to convert to UTF-16. We'll take ownership of the buffer
/// here.
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
text: U16String,
/// We include the lifetime in the declaration of the type still, so the borrow checker
/// complains, if we would mess up the compilation for narrow methods.
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
_ref: PhantomData<&'a str>,
/// In the case of narrow compiliation we just forward the string silce unchanged
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
text: &'a str,
}

impl<'a> SqlText<'a> {
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
/// Create an SqlText buffer from an UTF-8 string slice
pub fn new(text: &'a str) -> Self {
Self {
text: U16String::from_str(text),
_ref: PhantomData,
}
}
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
/// Create an SqlText buffer from an UTF-8 string slice
pub fn new(text: &'a str) -> Self {
Self { text }
}

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
pub fn ptr(&self) -> *const u16 {
buf_ptr(self.text.as_slice())
}
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
pub fn ptr(&self) -> *const u8 {
buf_ptr(self.text.as_bytes())
}
Expand Down
4 changes: 2 additions & 2 deletions odbc-api/src/handles/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ use std::{ffi::c_void, marker::PhantomData, mem::ManuallyDrop, num::NonZeroUsize
#[cfg(feature = "odbc_version_3_80")]
use odbc_sys::SQLCompleteAsync;

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
use odbc_sys::{
SQLColAttribute as sql_col_attribute, SQLColumns as sql_columns,
SQLDescribeCol as sql_describe_col, SQLExecDirect as sql_exec_direc,
SQLForeignKeys as sql_foreign_keys, SQLPrepare as sql_prepare,
SQLSetStmtAttr as sql_set_stmt_attr, SQLTables as sql_tables,
};

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
use odbc_sys::{
SQLColAttributeW as sql_col_attribute, SQLColumnsW as sql_columns,
SQLDescribeColW as sql_describe_col, SQLExecDirectW as sql_exec_direc,
Expand Down
18 changes: 9 additions & 9 deletions odbc-api/src/into_parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
Nullable,
};

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
use crate::parameter::{VarCharBox, VarCharSlice};

/// An instance can be consumed and to create a parameter which can be bound to a statement during
Expand Down Expand Up @@ -35,7 +35,7 @@ where
}
}

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
impl<'a> IntoParameter for &'a str {
type Parameter = VarCharSlice<'a>;

Expand All @@ -44,7 +44,7 @@ impl<'a> IntoParameter for &'a str {
}
}

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
impl<'a> IntoParameter for &'a str {
type Parameter = VarWCharBox;

Expand All @@ -59,15 +59,15 @@ impl<'a> IntoParameter for Option<&'a str> {
fn into_parameter(self) -> Self::Parameter {
match self {
Some(str) => str.into_parameter(),
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
None => VarCharSlice::NULL,
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
None => VarWCharBox::null(),
}
}
}

#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
impl IntoParameter for String {
type Parameter = VarCharBox;

Expand All @@ -76,7 +76,7 @@ impl IntoParameter for String {
}
}

#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
impl IntoParameter for String {
type Parameter = VarWCharBox;

Expand All @@ -91,9 +91,9 @@ impl IntoParameter for Option<String> {
fn into_parameter(self) -> Self::Parameter {
match self {
Some(str) => str.into_parameter(),
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
None => VarCharBox::null(),
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
None => VarWCharBox::null(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions odbc-api/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4159,7 +4159,7 @@ fn chinese_text_argument(profile: &Profile) {
.execute(&table.sql_all_ordered_by_id(), ())
.unwrap()
.unwrap();
#[cfg(not(feature = "narrow"))]
#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
let actual = {
let buffer = ColumnarBuffer::<_>::new(vec![(1, TextColumn::<u16>::new(1, 50))]);
let mut cursor = cursor.bind_buffer(buffer).unwrap();
Expand All @@ -4171,7 +4171,7 @@ fn chinese_text_argument(profile: &Profile) {

// Fetching non narrow should always work, but does not for PostgreSQL with narrow compilation
// flag.
#[cfg(feature = "narrow")]
#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
let actual = {
let buffer = ColumnarBuffer::<_>::new(vec![(1, TextColumn::<u8>::new(1, 50))]);
let mut cursor = cursor.bind_buffer(buffer).unwrap();
Expand Down

0 comments on commit 3e6b329

Please sign in to comment.