Skip to content

Commit

Permalink
Merge 670e65b into 328d3f4
Browse files Browse the repository at this point in the history
  • Loading branch information
pacman82 committed Sep 10, 2023
2 parents 328d3f4 + 670e65b commit bbe895a
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 10 deletions.
22 changes: 22 additions & 0 deletions Changelog.md
@@ -1,5 +1,27 @@
# Changelog

## (next)

* The `IntoParameter` implementation for `str` slices and `Strings` will now be converting them to `VarWCharBox` if the `narrow` compile time feature is **not** activated. This has been done in order to allow for easier writing of portable code between Windows and non-Windows platforms. Usually you would like to activate the `narrow` feature on non-windows platform there a UTF-8 default locale can be assumed. On Windows you want utilize UTF-16 encoding since it is the only reliable way to to transfer non ASCII characters independent from the systems locale. With this change on both platforms one can now simply write:

```rust
// Uses UTF-16 if `narrow` is not set. Otherwise directly binds the UTF-8 silce.
conn.execute(&insert_sql, &"Frühstück".into_parameter()).unwrap();
```

```rust
// Guaranteed to use UTF-16 independent of compiliation flags.
let arg = U16String::from_str("Frühstück");
conn.execute(&insert_sql, &arg.into_parameter()).unwrap();
```

```rust
// Guaranteed to use UTF-8 independent of compiliation flags. This relies on the ODBC driver to use
// UTF-8 encoding.
let arg = Narrow("Frühstück");
conn.execute(&insert_sql, &arg.into_parameter()).unwrap();
```

## 0.57.1

* Corrected typos in documentation. Thanks to @zachbateman
Expand Down
38 changes: 32 additions & 6 deletions odbc-api/src/into_parameter.rs
Expand Up @@ -3,13 +3,13 @@ use widestring::{U16Str, U16String};
use crate::{
buffers::Indicator,
fixed_sized::Pod,
parameter::{
InputParameter, VarBinaryBox, VarBinarySlice, VarCharBox, VarCharSlice, VarWCharBox,
VarWCharSlice,
},
parameter::{InputParameter, VarBinaryBox, VarBinarySlice, VarWCharBox, VarWCharSlice},
Nullable,
};

#[cfg(feature = "narrow")]
use crate::parameter::{VarCharBox, VarCharSlice};

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

#[cfg(feature = "narrow")]
impl<'a> IntoParameter for &'a str {
type Parameter = VarCharSlice<'a>;

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

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

fn into_parameter(self) -> Self::Parameter {
VarWCharBox::from_str_slice(self)
}
}

impl<'a> IntoParameter for Option<&'a str> {
type Parameter = VarCharSlice<'a>;
type Parameter = <&'a str as IntoParameter>::Parameter;

fn into_parameter(self) -> Self::Parameter {
match self {
Some(str) => str.into_parameter(),
#[cfg(feature = "narrow")]
None => VarCharSlice::NULL,
#[cfg(not(feature = "narrow"))]
None => VarWCharBox::null(),
}
}
}

#[cfg(feature = "narrow")]
impl IntoParameter for String {
type Parameter = VarCharBox;

Expand All @@ -62,13 +76,25 @@ impl IntoParameter for String {
}
}

#[cfg(not(feature = "narrow"))]
impl IntoParameter for String {
type Parameter = VarWCharBox;

fn into_parameter(self) -> Self::Parameter {
VarWCharBox::from_str_slice(&self)
}
}

impl IntoParameter for Option<String> {
type Parameter = VarCharBox;
type Parameter = <String as IntoParameter>::Parameter;

fn into_parameter(self) -> Self::Parameter {
match self {
Some(str) => str.into_parameter(),
#[cfg(feature = "narrow")]
None => VarCharBox::null(),
#[cfg(not(feature = "narrow"))]
None => VarWCharBox::null(),
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions odbc-api/src/lib.rs
Expand Up @@ -13,6 +13,7 @@ mod error;
mod execute;
mod fixed_sized;
mod into_parameter;
mod narrow;
mod nullable;
mod parameter_collection;
mod preallocated;
Expand All @@ -38,6 +39,7 @@ pub use self::{
fixed_sized::Bit,
handles::{ColumnDescription, DataType, Nullability},
into_parameter::IntoParameter,
narrow::Narrow,
nullable::Nullable,
parameter::{InOut, Out, OutputParameter},
parameter_collection::{ParameterCollection, ParameterCollectionRef, ParameterTupleElement},
Expand Down
65 changes: 65 additions & 0 deletions odbc-api/src/narrow.rs
@@ -0,0 +1,65 @@
use crate::{IntoParameter, parameter::{VarCharSlice, VarCharBox}};

/// Newtype wrapper intend to be used around `String`s or `str` slices to bind them always as narrow
/// text independent of wether the `narrow` feature is set or not.
pub struct Narrow<T>(pub T);

impl<'a> IntoParameter for Narrow<&'a str> {
type Parameter = VarCharSlice<'a>;

fn into_parameter(self) -> Self::Parameter {
VarCharSlice::new(self.0.as_bytes())
}
}

impl<'a> IntoParameter for Narrow<Option<&'a str>> {
type Parameter = VarCharSlice<'a>;

fn into_parameter(self) -> Self::Parameter {
match self.0 {
Some(str) => Narrow(str).into_parameter(),
None => VarCharSlice::NULL,
}
}
}

impl<'a> IntoParameter for Option<Narrow<&'a str>> {
type Parameter = VarCharSlice<'a>;

fn into_parameter(self) -> Self::Parameter {
match self {
Some(str) => Narrow(str.0).into_parameter(),
None => VarCharSlice::NULL,
}
}
}

impl IntoParameter for Narrow<String> {
type Parameter = VarCharBox;

fn into_parameter(self) -> Self::Parameter {
VarCharBox::from_string(self.0)
}
}

impl IntoParameter for Narrow<Option<String>> {
type Parameter = VarCharBox;

fn into_parameter(self) -> Self::Parameter {
match self.0 {
Some(str) => Narrow(str).into_parameter(),
None => VarCharBox::null(),
}
}
}

impl IntoParameter for Option<Narrow<String>> {
type Parameter = VarCharBox;

fn into_parameter(self) -> Self::Parameter {
match self {
Some(str) => Narrow(str.0).into_parameter(),
None => VarCharBox::null(),
}
}
}
7 changes: 7 additions & 0 deletions odbc-api/src/parameter/varcell.rs
Expand Up @@ -183,6 +183,13 @@ where
pub fn from_u16_string(val: U16String) -> Self {
Self::from_vec(val.into_vec())
}

/// Create an owned parameter containing the character data from the passed string. Converts it
/// to UTF-16 and allocates it.
pub fn from_str_slice(val: &str) -> Self {
let utf16 = U16String::from_str(val);
Self::from_u16_string(utf16)
}
}

impl<B, K> VarCell<B, K>
Expand Down
45 changes: 41 additions & 4 deletions odbc-api/tests/integration.rs
Expand Up @@ -18,7 +18,7 @@ use odbc_api::{
Blob, BlobRead, BlobSlice, VarBinaryArray, VarCharArray, VarCharSlice, WithDataType,
},
sys, Bit, ColumnDescription, Connection, ConnectionOptions, Cursor, DataType, Error, InOut,
IntoParameter, Nullability, Nullable, Out, ResultSetMetadata, U16Str, U16String,
IntoParameter, Nullability, Nullable, Out, ResultSetMetadata, U16Str, U16String, Narrow,
};
use std::{
ffi::CString,
Expand Down Expand Up @@ -1443,6 +1443,44 @@ fn bind_str_parameter_to_char(profile: &Profile) {
assert_eq!("Hello", actual);
}

#[test_case(MSSQL; "Microsoft SQL Server")]
#[test_case(MARIADB; "Maria DB")]
#[test_case(SQLITE_3; "SQLite 3")]
#[test_case(POSTGRES; "PostgreSQL")]
fn bind_narrow_parameter_to_varchar(profile: &Profile) {
let table_name = table_name!();
let (conn, table) = profile.given(&table_name, &["VARCHAR(10)"]).unwrap();
let insert_sql = table.sql_insert();

// String Slice
conn.execute(&insert_sql, &Narrow("Hello").into_parameter())
.unwrap();
// Option slice
conn.execute(&insert_sql, &Narrow(Some("Hello")).into_parameter())
.unwrap();
conn.execute(&insert_sql, &Narrow(None::<&str>).into_parameter())
.unwrap();
conn.execute(&insert_sql, &Some(Narrow("Hello")).into_parameter())
.unwrap();
conn.execute(&insert_sql, &None::<Narrow::<&str>>.into_parameter())
.unwrap();
// String
conn.execute(&insert_sql, &Narrow("Hello".to_string()).into_parameter())
.unwrap();
// Option String
conn.execute(&insert_sql, &Narrow(Some("Hello".to_string())).into_parameter())
.unwrap();
conn.execute(&insert_sql, &Narrow(None::<String>).into_parameter())
.unwrap();
conn.execute(&insert_sql, &Some(Narrow("Hello".to_string())).into_parameter())
.unwrap();
conn.execute(&insert_sql, &None::<Narrow::<String>>.into_parameter())
.unwrap();

let actual = table.content_as_string(&conn);
assert_eq!("Hello\nHello\nNULL\nHello\nNULL\nHello\nHello\nNULL\nHello\nNULL", actual);
}

#[test_case(MSSQL; "Microsoft SQL Server")]
#[test_case(MARIADB; "Maria DB")]
#[test_case(SQLITE_3; "SQLite 3")]
Expand Down Expand Up @@ -3727,8 +3765,7 @@ fn chinese_text_argument(profile: &Profile) {
let insert_sql = table.sql_insert();

// When
let arg = U16String::from_str("您好");
conn.execute(&insert_sql, &arg.into_parameter()).unwrap();
conn.execute(&insert_sql, &"您好".into_parameter()).unwrap();

// Then
let cursor = conn
Expand All @@ -3755,7 +3792,7 @@ fn chinese_text_argument_nvarchar(profile: &Profile) {
let insert_sql = table.sql_insert();

// When
let arg = U16String::from_str("您好");
let arg = U16String::from_str("您好"); // Narrow build will fail for MSSQL without this line.
conn.execute(&insert_sql, &arg.into_parameter()).unwrap();

// Then
Expand Down

0 comments on commit bbe895a

Please sign in to comment.