Skip to content

Commit

Permalink
Merge pull request #101 from yuana1/master
Browse files Browse the repository at this point in the history
 add prepare_bytes, exec_direct_bytes to support none utf8 environment
  • Loading branch information
Konstantin V. Salikhov committed Jan 22, 2019
2 parents a119374 + f20646a commit 99f0a50
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 2 deletions.
1 change: 1 addition & 0 deletions appveyor.yml
Expand Up @@ -103,6 +103,7 @@ matrix:
# default-toolchain and default-host manually here.
install:
- ps: |
chcp
Write-Host "Installing SQLite ODBC Driver:" -ForegroundColor Magenta
$sqliteodbc64Path = "$($env:TEMP)\sqliteodbc_w64.exe"
$sqliteodbc32Path = "$($env:TEMP)\sqliteodbc.exe"
Expand Down
42 changes: 40 additions & 2 deletions src/statement/mod.rs
Expand Up @@ -18,9 +18,9 @@ struct Chunks<T>(Vec<Box<[T; CHUNK_LEN]>>);
/// Heap allocator that will keep allocated element pointers valid until the allocator is dropped or cleared
impl<T: Copy + Default> Chunks<T> {
fn new() -> Chunks<T> {
Chunks(Vec::new())
Chunks(Vec::new())
}

fn alloc(&mut self, i: usize, value: T) -> *mut T {
let chunk_no = i / CHUNK_LEN;
if self.0.len() <= chunk_no {
Expand Down Expand Up @@ -146,6 +146,23 @@ impl<'a, 'b, 'env> Statement<'a, 'b, Allocated, NoResult> {
Ok(ResultSetState::NoData(Statement::with_raii(self.raii)))
}
}

/// Executes a preparable statement, using the current values of the parameter marker variables
/// if any parameters exist in the statement.
///
/// `SQLExecDirect` is the fastest way to submit an SQL statement for one-time execution.
pub fn exec_direct_bytes(mut self, bytes: &[u8]) -> Result<ResultSetState<'a, 'b, Executed>> {
if self.raii.exec_direct_bytes(bytes).into_result(&self)? {
let num_cols = self.raii.num_result_cols().into_result(&self)?;
if num_cols > 0 {
Ok(ResultSetState::Data(Statement::with_raii(self.raii)))
} else {
Ok(ResultSetState::NoData(Statement::with_raii(self.raii)))
}
} else {
Ok(ResultSetState::NoData(Statement::with_raii(self.raii)))
}
}
}

impl<'a, 'b, S> Statement<'a, 'b, S, HasResult> {
Expand Down Expand Up @@ -336,6 +353,27 @@ impl Raii<ffi::Stmt> {
}
}

fn exec_direct_bytes(&mut self, bytes: &[u8]) -> Return<bool> {
let length = bytes.len();
if length > ffi::SQLINTEGER::max_value() as usize {
panic!("Statement text too long");
}
match unsafe {
ffi::SQLExecDirect(
self.handle(),
bytes.as_ptr(),
length as ffi::SQLINTEGER,
)
} {
ffi::SQL_SUCCESS => Return::Success(true),
ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(true),
ffi::SQL_ERROR => Return::Error,
ffi::SQL_NEED_DATA => panic!("SQLExecDirec returned SQL_NEED_DATA"),
ffi::SQL_NO_DATA => Return::Success(false),
r => panic!("SQLExecDirect returned unexpected result: {:?}", r),
}
}

/// Fetches the next rowset of data from the result set and returns data for all bound columns.
fn fetch(&mut self) -> Return<bool> {
match unsafe { ffi::SQLFetch(self.handle()) } {
Expand Down
40 changes: 40 additions & 0 deletions src/statement/prepare.rs
Expand Up @@ -41,6 +41,31 @@ impl<'a, 'b> Statement<'a, 'b, Allocated, NoResult> {
self.raii.prepare(sql_text).into_result(&mut self)?;
Ok(Statement::with_raii(self.raii))
}


/// Prepares a statement for execution. Executing a prepared statement is faster than directly
/// executing an unprepared statement, since it is already compiled into an Access Plan. This
/// makes preparing statement a good idea if you want to repeatedly execute a query with a
/// different set of parameters and care about performance.
///
/// # Example
///
/// ```rust
/// # use odbc::*;
/// # fn doc() -> Result<()>{
/// let env = create_environment_v3().map_err(|e| e.unwrap())?;
/// let conn = env.connect("TestDataSource", "", "")?;
/// let stmt = Statement::with_parent(&conn)?;
/// // need encode_rs crate
/// // let mut stmt = stmt.prepare_bytes(&GB2312.encode("select '你好' as hello").0)?;
///
/// # Ok(())
/// # }
/// ```
pub fn prepare_bytes(mut self, bytes: &[u8]) -> Result<Statement<'a, 'b, Prepared, NoResult>> {
self.raii.prepare_byte(bytes).into_result(&mut self)?;
Ok(Statement::with_raii(self.raii))
}
}

impl<'a, 'b> Statement<'a, 'b, Prepared, NoResult> {
Expand Down Expand Up @@ -88,6 +113,21 @@ impl Raii<ffi::Stmt> {
}
}

fn prepare_byte(&mut self, bytes: &[u8]) -> Return<()> {
match unsafe {
ffi::SQLPrepare(
self.handle(),
bytes.as_ptr(),
bytes.len() as ffi::SQLINTEGER,
)
} {
ffi::SQL_SUCCESS => Return::Success(()),
ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(()),
ffi::SQL_ERROR => Return::Error,
r => panic!("SQLPrepare returned unexpected result: {:?}", r),
}
}

fn execute(&mut self) -> Return<bool> {
match unsafe { ffi::SQLExecute(self.handle()) } {
ffi::SQL_SUCCESS => Return::Success(true),
Expand Down
180 changes: 180 additions & 0 deletions tests/gbk.rs
@@ -0,0 +1,180 @@
//! Contains test for all types supporting `bind_parameter`
//!
//! These tests assume there is a Stage table with a Varchar in 'A', an Integer in 'B' and a Real
//! in 'C'
extern crate odbc;
use odbc::*;

#[test]
#[ignore]
/// tested in windows中文版 codepage 936
fn _exec_direct() {
let env = create_environment_v3().unwrap();
let conn = env.connect("TestDataSource", "", "").unwrap();
let stmt = Statement::with_parent(&conn).unwrap();

// select '你好' as hello
if let Ok(Data(mut stmt)) = stmt.exec_direct_bytes(vec![115, 101, 108, 101, 99, 116, 32, 39, 196, 227, 186, 195, 39, 32, 97, 115, 32, 104, 101, 108, 108, 111, 32].as_slice()) {
while let Some(mut cursor) = stmt.fetch().unwrap() {
match cursor.get_data::<Vec<u8>>(1).unwrap() {
Some(val) => assert_eq!(val, vec![196, 227, 186, 195]), // when 你好 is encoded by gbk, it is [196, 227, 186, 195]
None => panic!(" NULL"),
}
}
} else {
panic!("SELECT did not return result set");
}
}

#[test]
#[ignore]
/// tested in windows中文版 codepage 936
fn _prepare_1() {
let env = create_environment_v3().unwrap();
let conn = env.connect("TestDataSource", "", "").unwrap();
// select '你好' as hello where 1 = ?
let stmt = Statement::with_parent(&conn).unwrap().prepare_bytes(vec![115, 101, 108, 101, 99, 116, 32, 39, 196, 227, 186, 195, 39, 32, 97, 115, 32, 104, 101, 108, 108, 111, 32, 119, 104, 101, 114, 101, 32, 32, 49, 32, 61, 32, 63, 32].as_slice()).unwrap();
let stmt = stmt.bind_parameter(1, &1).unwrap();

match stmt.execute().unwrap() {
Data(mut stmt) => {
while let Some(mut cursor) = stmt.fetch().unwrap() {
match cursor.get_data::<Vec<u8>>(1).unwrap() {
Some(val) => assert_eq!(val, vec![196, 227, 186, 195]),
None => panic!(" NULL"),
}
}
}
NoData(_) => {
panic!("SELECT did not return result set");
}
}

}

#[test]
#[ignore]
/// tested in windows中文版 codepage 936
fn _prepare_2() {
let env = create_environment_v3().unwrap();
let conn = env.connect("TestDataSource", "sa", "123456").unwrap();
// select '你好' as hello where '你好' = ?
let stmt = Statement::with_parent(&conn).unwrap().prepare_bytes(vec![115, 101, 108, 101, 99, 116, 32, 39, 228, 189, 160, 229, 165, 189, 39, 32, 97, 115, 32, 104, 101, 108, 108, 111, 32, 119, 104, 101, 114, 101, 32, 39, 228, 189, 160, 229, 165, 189, 39, 32, 61, 32, 63].as_slice()).unwrap();
// bind gbk encoded byte
let param = CustomOdbcType {
data: &[228, 189, 160, 229, 165, 189]
};
let stmt = stmt.bind_parameter(1, &param).unwrap();

if let Ok(Data(mut stmt)) = stmt.execute() {
if let Some(mut cursor) = stmt.fetch().unwrap() {
match cursor.get_data::<Vec<u8>>(1).unwrap() {
Some(val) => assert_eq!(val, vec![196, 227, 186, 195]),
None => panic!(" NULL"),
}
} else {
panic!("No data")
}
} else {
panic!("SELECT did not return result set");
}
}

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

// select 'hello'
if let Ok(Data(mut stmt)) = stmt.exec_direct_bytes(vec![115, 101, 108, 101, 99, 116, 32, 39, 104, 101, 108, 108, 111, 39, 32].as_slice()) {
if let Some(mut cursor) = stmt.fetch().unwrap() {
match cursor.get_data::<Vec<u8>>(1).unwrap() {
Some(val) => assert_eq!(val, vec![104, 101, 108, 108, 111]), // when hello is encoded by utf8, it is [104, 101, 108, 108, 111]
None => panic!(" NULL"),
}
} else {
panic!("No Data");
}
} else {
panic!("SELECT did not return result set");
}
}

#[test]
fn prepare_1() {
let env = create_environment_v3().unwrap();
let conn = env.connect("TestDataSource", "", "").unwrap();
// select 'hello' where 'hello' = ?
let stmt = Statement::with_parent(&conn).unwrap().prepare_bytes(vec![115, 101, 108, 101, 99, 116, 32, 39, 104, 101, 108, 108, 111, 39, 32, 119, 104, 101, 114, 101, 32, 39, 104, 101, 108, 108, 111, 39, 32, 61, 32, 63, 32].as_slice()).unwrap();
let stmt = stmt.bind_parameter(1, &"hello").unwrap();

if let Ok(Data(mut stmt)) = stmt.execute() {
if let Some(mut cursor) = stmt.fetch().unwrap() {
match cursor.get_data::<Vec<u8>>(1).unwrap() {
Some(val) => assert_eq!(val, vec![104, 101, 108, 108, 111]),
None => panic!(" NULL"),
}
} else {
panic!("No data");
}
} else {
panic!("SELECT did not return result set");
}

}

/// CustomOdbcType for bindParameter
struct CustomOdbcType<'a> {
data: &'a [u8],
}

unsafe impl<'a> OdbcType<'a> for CustomOdbcType<'a> {
fn sql_data_type() -> ffi::SqlDataType {
ffi::SQL_VARCHAR
}
fn c_data_type() -> ffi::SqlCDataType {
ffi::SQL_C_CHAR
}

fn convert(buffer: &'a [u8]) -> Self {
CustomOdbcType {
data: buffer
}
}

fn column_size(&self) -> ffi::SQLULEN {
self.data.len() as ffi::SQLULEN
}

fn value_ptr(&self) -> ffi::SQLPOINTER {
self.data.as_ptr() as *const Self as ffi::SQLPOINTER
}
}

#[test]
fn prepare_2() {
let env = create_environment_v3().unwrap();
let conn = env.connect("TestDataSource", "", "").unwrap();
// select 'hello' where 'hello' = ?
let stmt = Statement::with_parent(&conn).unwrap().prepare_bytes(vec![115, 101, 108, 101, 99, 116, 32, 39, 104, 101, 108, 108, 111, 39, 32, 119, 104, 101, 114, 101, 32, 39, 104, 101, 108, 108, 111, 39, 32, 61, 32, 63, 32].as_slice()).unwrap();
// bind utf encoded byte
// let param: Vec<u8> = vec![104, 101, 108, 108, 111];
let param = CustomOdbcType {
data: &[104, 101, 108, 108, 111]
};
let stmt = stmt.bind_parameter(1, &param).unwrap();

if let Ok(Data(mut stmt)) = stmt.execute() {
if let Some(mut cursor) = stmt.fetch().unwrap() {
match cursor.get_data::<Vec<u8>>(1).unwrap() {
Some(val) => assert_eq!(val, vec![104, 101, 108, 108, 111]),
None => panic!(" NULL"),
}
} else {
panic!("No data");
}
} else {
panic!("SELECT did not return result set");
}
}

0 comments on commit 99f0a50

Please sign in to comment.