mod types;
mod input;
mod output;
mod prepare;
pub use self::output::Output;
use {ffi, safe, Connection, Return, Result, Raii, Handle};
use ffi::SQLRETURN::*;
use ffi::Nullable;
use std::marker::PhantomData;
pub use self::types::OdbcType;
pub use self::types::{SqlDate, SqlTime, SqlSsTime2, SqlTimestamp, EncodedValue};
const CHUNK_LEN: usize = 64;
struct Chunks<T>(Vec<Box<[T; CHUNK_LEN]>>);
impl<T: Copy + Default> Chunks<T> {
fn new() -> Chunks<T> {
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 {
self.0.resize(chunk_no + 1, Box::new([T::default(); CHUNK_LEN]))
}
let v = self.0[chunk_no].get_mut(i % CHUNK_LEN).unwrap();
*v = value;
v as *mut T
}
fn clear(&mut self) {
self.0.clear()
}
}
pub enum Allocated {}
pub type Executed = Allocated;
pub enum Prepared {}
pub enum HasResult {}
pub enum NoResult {}
pub enum ResultSetState<'a, 'b, S, AC: AutocommitMode> {
Data(Statement<'a, 'b, S, HasResult, AC>),
NoData(Statement<'a, 'b, S, NoResult, AC>),
}
pub use ResultSetState::*;
use std::ptr::null_mut;
use odbc_safe::AutocommitMode;
pub struct Statement<'a, 'b, S, R, AC: AutocommitMode> {
raii: Raii<'a, ffi::Stmt>,
state: PhantomData<S>,
autocommit_mode: PhantomData<AC>,
result: PhantomData<R>,
parameters: PhantomData<&'b [u8]>,
param_ind_buffers: Chunks<ffi::SQLLEN>,
encoded_values: Vec<EncodedValue>,
}
pub struct Cursor<'s, 'a: 's, 'b: 's, S: 's, AC: AutocommitMode> {
stmt: &'s mut Statement<'a, 'b, S, HasResult, AC>,
buffer: Vec<u8>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ColumnDescriptor {
pub name: String,
pub data_type: ffi::SqlDataType,
pub column_size: Option<ffi::SQLULEN>,
pub decimal_digits: Option<u16>,
pub nullable: Option<bool>,
}
impl<'a, 'b, S, R, AC: AutocommitMode> Handle for Statement<'a, 'b, S, R, AC> {
type To = ffi::Stmt;
unsafe fn handle(&self) -> ffi::SQLHSTMT {
self.raii.handle()
}
}
impl<'a, 'b, S, R, AC: AutocommitMode> Statement<'a, 'b, S, R, AC> {
fn with_raii(raii: Raii<'a, ffi::Stmt>) -> Self {
Statement {
raii: raii,
autocommit_mode: PhantomData,
state: PhantomData,
result: PhantomData,
parameters: PhantomData,
param_ind_buffers: Chunks::new(),
encoded_values: Vec::new(),
}
}
}
impl<'a, 'b, 'env, AC: AutocommitMode> Statement<'a, 'b, Allocated, NoResult, AC> {
pub fn with_parent(ds: &'a Connection<'env, AC>) -> Result<Self> {
let raii = Raii::with_parent(ds).into_result(ds)?;
Ok(Self::with_raii(raii))
}
pub fn affected_row_count(&self) -> Result<ffi::SQLLEN> {
self.raii.affected_row_count().into_result(self)
}
pub fn tables(self, catalog_name: &String, schema_name: &String, table_name: &String, table_type: &String) -> Result<Statement<'a, 'b, Executed, HasResult, AC>> {
self.tables_str(catalog_name.as_str(), schema_name.as_str(), table_name.as_str(), table_type.as_str())
}
pub fn tables_str(self, catalog_name: &str, schema_name: &str, table_name: &str, table_type: &str) -> Result<Statement<'a, 'b, Executed, HasResult, AC>> {
self.tables_opt_str(Option::Some(catalog_name), Option::Some(schema_name), Option::Some(table_name), table_type)
}
pub fn tables_opt_str(mut self, catalog_name: Option<&str>, schema_name: Option<&str>, table_name:Option<&str>, table_type: &str) -> Result<Statement<'a, 'b, Executed, HasResult, AC>> {
self.raii.tables(catalog_name, schema_name, table_name, table_type).into_result(&self)?;
Ok(Statement::with_raii(self.raii))
}
pub fn exec_direct(mut self, statement_text: &str) -> Result<ResultSetState<'a, 'b, Executed, AC>> {
if self.raii.exec_direct(statement_text).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)))
}
}
pub fn exec_direct_bytes(mut self, bytes: &[u8]) -> Result<ResultSetState<'a, 'b, Executed, AC>> {
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, AC: AutocommitMode> Statement<'a, 'b, S, HasResult, AC> {
pub fn affected_row_count(&self) -> Result<ffi::SQLLEN> {
self.raii.affected_row_count().into_result(self)
}
pub fn num_result_cols(&self) -> Result<i16> {
self.raii.num_result_cols().into_result(self)
}
pub fn describe_col(&self, idx: u16) -> Result<ColumnDescriptor> {
self.raii.describe_col(idx).into_result(self)
}
pub fn fetch<'s>(&'s mut self) -> Result<Option<Cursor<'s, 'a, 'b, S, AC>>> {
if self.raii.fetch().into_result(self)? {
Ok(Some(Cursor {
stmt: self,
buffer: vec![0; 512],
}))
} else {
Ok(None)
}
}
pub fn close_cursor(mut self) -> Result<Statement<'a, 'b, S, NoResult, AC>> {
self.raii.close_cursor().into_result(&self)?;
Ok(Statement::with_raii(self.raii))
}
}
impl<'a, 'b, 'c, S, AC: AutocommitMode> Cursor<'a, 'b, 'c, S, AC> {
pub fn get_data<'d, T>(&'d mut self, col_or_param_num: u16) -> Result<Option<T>>
where
T: Output<'d>,
{
T::get_data(&mut self.stmt.raii, col_or_param_num, &mut self.buffer).into_result(self.stmt)
}
}
impl<'p> Raii<'p, ffi::Stmt> {
fn affected_row_count(&self) -> Return<ffi::SQLLEN> {
let mut count: ffi::SQLLEN = 0;
unsafe {
match ffi::SQLRowCount(self.handle(), &mut count as *mut ffi::SQLLEN) {
SQL_SUCCESS => Return::Success(count),
SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(count),
SQL_ERROR => Return::Error,
r => panic!("SQLRowCount returned unexpected result: {:?}", r),
}
}
}
fn num_result_cols(&self) -> Return<i16> {
let mut num_cols: ffi::SQLSMALLINT = 0;
unsafe {
match ffi::SQLNumResultCols(self.handle(), &mut num_cols as *mut ffi::SQLSMALLINT) {
SQL_SUCCESS => Return::Success(num_cols),
SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(num_cols),
SQL_ERROR => Return::Error,
r => panic!("SQLNumResultCols returned unexpected result: {:?}", r),
}
}
}
fn describe_col(&self, idx: u16) -> Return<ColumnDescriptor> {
let mut name_buffer: [u8; 512] = [0; 512];
let mut name_length: ffi::SQLSMALLINT = 0;
let mut data_type: ffi::SqlDataType = ffi::SqlDataType::SQL_UNKNOWN_TYPE;
let mut column_size: ffi::SQLULEN = 0;
let mut decimal_digits: ffi::SQLSMALLINT = 0;
let mut nullable: Nullable = Nullable::SQL_NULLABLE_UNKNOWN;
unsafe {
match ffi::SQLDescribeCol(
self.handle(),
idx,
name_buffer.as_mut_ptr(),
name_buffer.len() as ffi::SQLSMALLINT,
&mut name_length as *mut ffi::SQLSMALLINT,
&mut data_type as *mut ffi::SqlDataType,
&mut column_size as *mut ffi::SQLULEN,
&mut decimal_digits as *mut ffi::SQLSMALLINT,
&mut nullable as *mut ffi::Nullable,
) {
SQL_SUCCESS => Return::Success(ColumnDescriptor {
name: ::environment::DB_ENCODING.decode(&name_buffer[..(name_length as usize)]).0
.to_string(),
data_type: data_type,
column_size: if column_size == 0 {
None
} else {
Some(column_size)
},
decimal_digits: if decimal_digits == 0 {
None
} else {
Some(decimal_digits as u16)
},
nullable: match nullable {
Nullable::SQL_NULLABLE_UNKNOWN => None,
Nullable::SQL_NULLABLE => Some(true),
Nullable::SQL_NO_NULLS => Some(false),
},
}),
SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(ColumnDescriptor {
name: ::environment::DB_ENCODING.decode(&name_buffer[..(name_length as usize)]).0
.to_string(),
data_type: data_type,
column_size: if column_size == 0 {
None
} else {
Some(column_size)
},
decimal_digits: if decimal_digits == 0 {
None
} else {
Some(decimal_digits as u16)
},
nullable: match nullable {
Nullable::SQL_NULLABLE_UNKNOWN => None,
Nullable::SQL_NULLABLE => Some(true),
Nullable::SQL_NO_NULLS => Some(false),
},
}),
SQL_ERROR => Return::Error,
r => panic!("SQLDescribeCol returned unexpected result: {:?}", r),
}
}
}
fn exec_direct(&mut self, statement_text: &str) -> Return<bool> {
let bytes = unsafe { crate::environment::DB_ENCODING }.encode(statement_text).0;
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),
}
}
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),
}
}
fn fetch(&mut self) -> Return<bool> {
match unsafe { ffi::SQLFetch(self.handle()) } {
ffi::SQL_SUCCESS => Return::Success(true),
ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(true),
ffi::SQL_ERROR => Return::Error,
ffi::SQL_NO_DATA => Return::Success(false),
r => panic!("SQLFetch returned unexpected result: {:?}", r),
}
}
fn tables(&mut self, catalog_name: Option<&str>, schema_name: Option<&str>, table_name: Option<&str>, table_type: &str) -> Return<()> {
unsafe {
let mut catalog: *const odbc_sys::SQLCHAR = null_mut();
let mut schema: *const odbc_sys::SQLCHAR = null_mut();
let mut table: *const odbc_sys::SQLCHAR = null_mut();
let mut catalog_size: odbc_sys::SQLSMALLINT = 0;
let mut schema_size: odbc_sys::SQLSMALLINT = 0;
let mut table_size: odbc_sys::SQLSMALLINT = 0;
if catalog_name.is_some() {
catalog = catalog_name.unwrap().as_ptr();
catalog_size = catalog_name.unwrap().len() as odbc_sys::SQLSMALLINT;
}
if schema_name.is_some() {
schema = schema_name.unwrap().as_ptr();
schema_size = schema_name.unwrap().len() as odbc_sys::SQLSMALLINT;
}
if table_name.is_some() {
table = table_name.unwrap().as_ptr();
table_size = table_name.unwrap().len() as odbc_sys::SQLSMALLINT;
}
match odbc_sys::SQLTables(
self.handle(),
catalog,
catalog_size,
schema,
schema_size,
table,
table_size,
table_type.as_ptr(),
table_type.as_bytes().len() as odbc_sys::SQLSMALLINT,
) {
SQL_SUCCESS => Return::Success(()),
SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(()),
SQL_ERROR => Return::Error,
r => panic!("SQLTables returned: {:?}", r),
}
}
}
fn close_cursor(&mut self) -> Return<()> {
unsafe {
match ffi::SQLCloseCursor(self.handle()) {
ffi::SQL_SUCCESS => Return::Success(()),
ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(()),
ffi::SQL_ERROR => Return::Error,
r => panic!("unexpected return value from SQLCloseCursor: {:?}", r),
}
}
}
}
unsafe impl<'con, 'param, C, P, AC: AutocommitMode> safe::Handle for Statement<'con, 'param, C, P, AC> {
const HANDLE_TYPE : ffi::HandleType = ffi::SQL_HANDLE_STMT;
fn handle(&self) -> ffi::SQLHANDLE {
<Raii<ffi::Stmt> as safe::Handle>::handle(&self.raii)
}
}