diff --git a/Cargo.lock b/Cargo.lock index 23cb05ec..01a45940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,7 +897,7 @@ dependencies = [ [[package]] name = "odbc-api" -version = "0.56.1" +version = "0.56.2" dependencies = [ "anyhow", "criterion", diff --git a/Changelog.md b/Changelog.md index 3592a53d..28741a64 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ # Changelog +## 0.56.2 + +* Support `U16Str` and `Option` as input parameter via `IntoParameter` trait. + ## 0.56.1 * Fix: `Statement::complete_async` is now annotated to be only available in ODBC version 3.8. This missing annotation prevented compilation then specifying ODBC version 3.5. diff --git a/odbc-api/Cargo.toml b/odbc-api/Cargo.toml index b38fd6a0..e6cffc03 100644 --- a/odbc-api/Cargo.toml +++ b/odbc-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "odbc-api" -version = "0.56.1" +version = "0.56.2" authors = ["Markus Klein"] edition = "2021" license = "MIT" diff --git a/odbc-api/src/into_parameter.rs b/odbc-api/src/into_parameter.rs index bdee5973..e6f9d0e0 100644 --- a/odbc-api/src/into_parameter.rs +++ b/odbc-api/src/into_parameter.rs @@ -1,7 +1,9 @@ +use widestring::U16Str; + use crate::{ fixed_sized::Pod, - parameter::{InputParameter, VarBinaryBox, VarBinarySlice, VarCharBox, VarCharSlice}, - Nullable, + parameter::{InputParameter, VarBinaryBox, VarBinarySlice, VarCharBox, VarCharSlice, VarWCharSlice}, + Nullable, buffers::Indicator, }; /// An instance can be consumed and to create a parameter which can be bound to a statement during @@ -105,6 +107,27 @@ impl IntoParameter for Option> { } } +impl<'a> IntoParameter for &'a U16Str { + type Parameter = VarWCharSlice<'a>; + + fn into_parameter(self) -> Self::Parameter { + let slice = self.as_slice(); + let (_, bytes, _) = unsafe { slice.align_to::() }; + VarWCharSlice::from_buffer(bytes, Indicator::Length(bytes.len())) + } +} + +impl<'a> IntoParameter for Option<&'a U16Str> { + type Parameter = VarWCharSlice<'a>; + + fn into_parameter(self) -> Self::Parameter { + match self { + Some(str) => str.into_parameter(), + None => VarWCharSlice::NULL, + } + } +} + impl IntoParameter for Option where T: Pod + InputParameter, diff --git a/odbc-api/src/parameter.rs b/odbc-api/src/parameter.rs index fe7b2993..dead6820 100644 --- a/odbc-api/src/parameter.rs +++ b/odbc-api/src/parameter.rs @@ -346,6 +346,9 @@ pub use self::{ }, }; +/// Currenty not made public due to unsure if it might not be better to implement this via u16 +pub(crate) use self::varcell::VarWCharSlice; + use std::ffi::c_void; use odbc_sys::CDataType; diff --git a/odbc-api/src/parameter/varcell.rs b/odbc-api/src/parameter/varcell.rs index 64d02287..26edae50 100644 --- a/odbc-api/src/parameter/varcell.rs +++ b/odbc-api/src/parameter/varcell.rs @@ -43,6 +43,24 @@ unsafe impl VarKind for Text { } } +/// Intended to be used as a generic argument for [`VariadicCell`] to declare that this buffer is +/// used to hold wide UTF-16 (as opposed to narrow ASCII or UTF-8) text. Use this to annotate binary +/// buffers (`[u8]`), rather than `[u16]` buffers. +pub struct WideTextBytes; + +unsafe impl VarKind for WideTextBytes { + const TERMINATING_ZEROES: usize = 2; + const C_DATA_TYPE: CDataType = CDataType::WChar; + + fn relational_type(length: usize) -> DataType { + // Since we might use as an input buffer, we report the full buffer length in the type and + // do not deduct 2 for the terminating zero. + // + // Also the length is in bytes and needs to be converted to characters. + DataType::WVarchar { length: length / 2 } + } +} + /// Intended to be used as a generic argument for [`VariadicCell`] to declare that this buffer is /// used to hold raw binary input. pub struct Binary; @@ -88,6 +106,7 @@ pub struct VarCell { pub type VarBinary = VarCell; pub type VarChar = VarCell; +pub type VarWChar = VarCell; /// Parameter type for owned, variable sized character data. /// @@ -335,6 +354,8 @@ where /// ``` pub type VarCharSlice<'a> = VarChar<&'a [u8]>; +pub type VarWCharSlice<'a> = VarWChar<&'a [u8]>; + /// Binds a byte array as a variadic binary input parameter. /// /// While a byte array can provide us with a pointer to the start of the array and the length of the diff --git a/odbc-api/tests/integration.rs b/odbc-api/tests/integration.rs index 6c31e71f..7fd2de78 100644 --- a/odbc-api/tests/integration.rs +++ b/odbc-api/tests/integration.rs @@ -324,35 +324,11 @@ fn column_name(profile: &Profile) { assert_eq!("b", desc.name_to_string().unwrap()); } -/// Bind a CHAR column to a character buffer. #[test_case(MSSQL; "Microsoft SQL Server")] #[test_case(MARIADB; "Maria DB")] #[test_case(SQLITE_3; "SQLite 3")] #[test_case(POSTGRES; "PostgreSQL")] -fn bind_char(profile: &Profile) { - let table_name = table_name!(); - let (conn, table) = profile.given(&table_name, &["CHAR(5)"]).unwrap(); - let insert_sql = table.sql_insert(); - conn.execute(&insert_sql, &"Hello".into_parameter()) - .unwrap(); - - let cursor = conn - .execute(&table.sql_all_ordered_by_id(), ()) - .unwrap() - .unwrap(); - let mut buf = ColumnarBuffer::new(vec![(1, TextColumn::new(1, 5))]); - let mut row_set_cursor = cursor.bind_buffer(&mut buf).unwrap(); - let batch = row_set_cursor.fetch().unwrap().unwrap(); - - assert_eq!(Some(&b"Hello"[..]), batch.column(0).get(0)); -} - -/// Bind a CHAR column to a wchar buffer -#[test_case(MSSQL; "Microsoft SQL Server")] -#[test_case(MARIADB; "Maria DB")] -#[test_case(SQLITE_3; "SQLite 3")] -#[test_case(POSTGRES; "PostgreSQL")] -fn bind_char_to_wchar(profile: &Profile) { +fn bind_wide_column_to_char(profile: &Profile) { let table_name = table_name!(); let (conn, table) = profile.given(&table_name, &["CHAR(5)"]).unwrap(); let insert_sql = table.sql_insert(); @@ -1451,6 +1427,39 @@ fn wchar_as_char(profile: &Profile) { assert_eq!("A\nÜ", table.content_as_string(&conn)); } +#[test_case(MSSQL; "Microsoft SQL Server")] +#[test_case(MARIADB; "Maria DB")] +#[test_case(SQLITE_3; "SQLite 3")] +#[test_case(POSTGRES; "PostgreSQL")] +fn bind_str_parameter_to_char(profile: &Profile) { + let table_name = table_name!(); + let (conn, table) = profile.given(&table_name, &["CHAR(5)"]).unwrap(); + let insert_sql = table.sql_insert(); + + conn.execute(&insert_sql, &"Hello".into_parameter()) + .unwrap(); + + let actual = table.content_as_string(&conn); + 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_u16str_parameter_to_char(profile: &Profile) { + let table_name = table_name!(); + let (conn, table) = profile.given(&table_name, &["CHAR(5)"]).unwrap(); + let insert_sql = table.sql_insert(); + + let hello = U16String::from_str("Hello"); + conn.execute(&insert_sql, &hello.into_parameter()) + .unwrap(); + + let actual = table.content_as_string(&conn); + assert_eq!("Hello", &actual); +} + #[test_case(MSSQL; "Microsoft SQL Server")] #[test_case(MARIADB; "Maria DB")] #[test_case(SQLITE_3; "SQLite 3")] diff --git a/odbcsv/Cargo.toml b/odbcsv/Cargo.toml index a8771e94..e7ea15df 100644 --- a/odbcsv/Cargo.toml +++ b/odbcsv/Cargo.toml @@ -29,7 +29,7 @@ readme = "Readme.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -odbc-api = { version = "0.56.1", path = "../odbc-api" } +odbc-api = { version = "0.56.2", path = "../odbc-api" } csv = "1.2.1" anyhow = "1.0.70" stderrlog = "0.5.4"