Skip to content

Commit

Permalink
Merge pull request #109 from yekimov/master
Browse files Browse the repository at this point in the history
Zeroes truncation bug fix.
  • Loading branch information
Konstantin V. Salikhov committed Apr 15, 2019
2 parents d6fcefb + 570169d commit f95fff1
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 9 deletions.
11 changes: 8 additions & 3 deletions src/statement/output.rs
Expand Up @@ -73,16 +73,21 @@ impl Raii<ffi::Stmt> {
}
ffi::SQL_SUCCESS_WITH_INFO => {
let initial_len = buffer.len();
// As a workaround for drivers that don't include tailing null(s) check if last bytes are null
let null_offset = if buffer.ends_with(T::null_bytes()) { T::null_bytes().len() } else { 0 };
// // As a workaround for drivers that don't include tailing null(s) check if last bytes are null
// let null_offset = if buffer.ends_with(T::null_bytes()) { T::null_bytes().len() } else { 0 };

// (Alexander Yekimov <a.yekimov@gmail.com>) It's a bad idea to do such workarounds
// for buggy drivers here. They always can implement OdbcType trait and set any
// amount of null-terminators to do the workaround.

let null_offset = T::null_bytes_count();
if indicator == ffi::SQL_NO_TOTAL {
buffer.resize(initial_len * 2, 0);
return self.get_partial_data(col_or_param_num, buffer, initial_len - null_offset);
} else {
// Check if string has been truncated.
if indicator >= initial_len as ffi::SQLLEN {
buffer.resize(indicator as usize + T::null_bytes().len(), 0);
buffer.resize(indicator as usize + T::null_bytes_count(), 0);
return self.get_partial_data(col_or_param_num, buffer, initial_len - null_offset);
} else {
let slice = &buffer[..(start_pos + indicator as usize)];
Expand Down
29 changes: 23 additions & 6 deletions src/statement/types.rs
Expand Up @@ -9,8 +9,8 @@ pub unsafe trait OdbcType<'a>: Sized {
fn c_data_type() -> ffi::SqlCDataType;
fn convert(_: &'a [u8]) -> Self;
fn column_size(&self) -> ffi::SQLULEN;
fn null_bytes() -> &'static [u8] {
&[0]
fn null_bytes_count() -> usize {
0
}
fn value_ptr(&self) -> ffi::SQLPOINTER;
fn decimal_digits(&self) -> ffi::SQLSMALLINT {
Expand Down Expand Up @@ -76,8 +76,8 @@ unsafe impl<'a> OdbcType<'a> for &'a[u16] {
(self.len() * 2) as ffi::SQLULEN
}

fn null_bytes() -> &'static [u8] {
&[0, 0]
fn null_bytes_count() -> usize {
2
}

fn value_ptr(&self) -> ffi::SQLPOINTER {
Expand All @@ -102,8 +102,8 @@ unsafe impl<'a> OdbcType<'a> for Vec<u16> {
(self.len() * 2) as ffi::SQLULEN
}

fn null_bytes() -> &'static [u8] {
&[0, 0]
fn null_bytes_count() -> usize {
2
}

fn value_ptr(&self) -> ffi::SQLPOINTER {
Expand All @@ -130,6 +130,10 @@ unsafe impl<'a> OdbcType<'a> for CString {
fn value_ptr(&self) -> ffi::SQLPOINTER {
self.as_bytes().as_ptr() as *const Self as ffi::SQLPOINTER
}

fn null_bytes_count() -> usize {
1
}
}

unsafe impl<'a> OdbcType<'a> for String {
Expand All @@ -151,6 +155,10 @@ unsafe impl<'a> OdbcType<'a> for String {
fn value_ptr(&self) -> ffi::SQLPOINTER {
self.as_bytes().as_ptr() as *const Self as ffi::SQLPOINTER
}

fn null_bytes_count() -> usize {
1
}
}

unsafe impl<'a> OdbcType<'a> for &'a str {
Expand All @@ -172,6 +180,10 @@ unsafe impl<'a> OdbcType<'a> for &'a str {
fn value_ptr(&self) -> ffi::SQLPOINTER {
self.as_bytes().as_ptr() as *const Self as ffi::SQLPOINTER
}

fn null_bytes_count() -> usize {
1
}
}

fn convert_primitive<T>(buf: &[u8]) -> T
Expand Down Expand Up @@ -511,6 +523,7 @@ unsafe impl<'a, T> OdbcType<'a> for Option<T> where T: OdbcType<'a> {
fn sql_data_type() -> ffi::SqlDataType {
T::sql_data_type()
}

fn c_data_type() -> ffi::SqlCDataType {
T::c_data_type()
}
Expand All @@ -533,4 +546,8 @@ unsafe impl<'a, T> OdbcType<'a> for Option<T> where T: OdbcType<'a> {
0 as *const Self as ffi::SQLPOINTER
}
}

fn null_bytes_count() -> usize {
T::null_bytes_count()
}
}
80 changes: 80 additions & 0 deletions tests/libs.rs
Expand Up @@ -268,3 +268,83 @@ fn list_system_data_sources() {
let expected: [DataSourceInfo; 0] = [];
assert!(sources.iter().eq(expected.iter()));
}

#[test]
fn read_big_string() {
let env = create_environment_v3().unwrap();
let conn = env.connect("TestDataSource", "", "").unwrap();
let stmt = Statement::with_parent(&conn).unwrap();

let stmt = match stmt.exec_direct("CREATE TABLE READ_BIG_STRING (DATA TEXT)").unwrap() {
Data(stmt) => stmt.close_cursor().unwrap(),
NoData(stmt) => stmt,
};
let data = "Hello, World".repeat(43);
assert!(data.len() > 512);
stmt
.prepare("INSERT INTO READ_BIG_STRING VALUES (?)")
.unwrap()
.bind_parameter(1, &data)
.unwrap()
.execute()
.unwrap();
let stmt = Statement::with_parent(&conn).unwrap();
let sel_query = "SELECT DATA FROM READ_BIG_STRING";
let stmt = if let Data(mut stmt) = stmt.exec_direct(sel_query).unwrap() {
{
let mut cursor = stmt.fetch().unwrap().unwrap();
// Do read with bytes buffer
let data0 = cursor.get_data::<Vec<u8>>(1).unwrap().unwrap();
assert_eq!(data0, data.as_bytes());
}
stmt.close_cursor().unwrap()
} else {
panic!("SELECT statement returned no result set")
};
let stmt = if let Data(mut stmt) = stmt.exec_direct(sel_query).unwrap() {
{
let mut cursor = stmt.fetch().unwrap().unwrap();
// Do read with String buffer
let data0 = cursor.get_data::<String>(1).unwrap().unwrap();
assert_eq!(data0, data);
}
stmt.close_cursor().unwrap()
} else {
panic!("SELECT statement returned no result set")
};
stmt.exec_direct("DROP TABLE READ_BIG_STRING").unwrap();
}

#[test]
fn zero_truncation_bug() {
let env = create_environment_v3().unwrap();
let conn = env.connect("TestDataSource", "", "").unwrap();
let stmt = Statement::with_parent(&conn).unwrap();

let stmt = match stmt.exec_direct("CREATE TABLE ZERO_TRUNCATION_BUG (DATA BLOB);").unwrap() {
Data(stmt) => stmt.close_cursor().unwrap(),
NoData(stmt) => stmt,
};
// Reproduction of zeroes truncation bug. Until now there is no chance to query binary data
// with zero at 512 byte border.
let data = vec![0;513];
stmt
.prepare("INSERT INTO ZERO_TRUNCATION_BUG VALUES (?)")
.unwrap()
.bind_parameter(1, &data)
.unwrap()
.execute()
.unwrap();
let stmt = Statement::with_parent(&conn).unwrap();
if let Data(mut stmt) = stmt.exec_direct("SELECT DATA FROM ZERO_TRUNCATION_BUG").unwrap() {
{
let mut cursor = stmt.fetch().unwrap().unwrap();
let data0 = cursor.get_data::<Vec<u8>>(1).unwrap().unwrap();
assert_eq!(data0, data);
}
let stmt = stmt.close_cursor().unwrap();
stmt.exec_direct("DROP TABLE ZERO_TRUNCATION_BUG").unwrap();
} else {
panic!("SELECT statement returned no result set")
}
}

0 comments on commit f95fff1

Please sign in to comment.