Skip to main content

odbc/statement/
mod.rs

1
2mod types;
3mod input;
4mod output;
5mod prepare;
6pub use self::output::Output;
7use {ffi, safe, Connection, Return, Result, Raii, Handle};
8use ffi::SQLRETURN::*;
9use ffi::Nullable;
10use std::marker::PhantomData;
11pub use self::types::OdbcType;
12pub use self::types::{SqlDate, SqlTime, SqlSsTime2, SqlTimestamp, EncodedValue};
13
14// Allocate CHUNK_LEN elements at a time
15const CHUNK_LEN: usize = 64;
16struct Chunks<T>(Vec<Box<[T; CHUNK_LEN]>>);
17
18/// Heap allocator that will keep allocated element pointers valid until the allocator is dropped or cleared
19impl<T: Copy + Default> Chunks<T> {
20    fn new() -> Chunks<T> {
21        Chunks(Vec::new())
22    }
23
24    fn alloc(&mut self, i: usize, value: T) -> *mut T {
25        let chunk_no = i / CHUNK_LEN;
26        if self.0.len() <= chunk_no {
27            // Resizing Vec that holds pointers to heap allocated arrays so we don't invalidate the references
28            self.0.resize(chunk_no + 1, Box::new([T::default(); CHUNK_LEN]))
29        }
30        let v = self.0[chunk_no].get_mut(i % CHUNK_LEN).unwrap();
31        *v = value;
32        v as *mut T
33    }
34
35    fn clear(&mut self) {
36        self.0.clear()
37    }
38}
39
40/// `Statement` state used to represent a freshly allocated connection
41pub enum Allocated {}
42/// `Statement` state used to represent a statement with a result set cursor. A statement is most
43/// likely to enter this state after a `SELECT` query.
44pub type Executed = Allocated;
45/// `Statement` state used to represent a statement compiled into an access plan. A statement will
46/// enter this state after a call to `Statement::prepared`
47pub enum Prepared {}
48/// `Statement` state used to represent a statement with a result set cursor. A statement is most
49/// likely to enter this state after a `SELECT` query.
50pub enum HasResult {}
51/// `Statement` state used to represent a statement with no result set. A statement is likely to
52/// enter this state after executing e.g. a `CREATE TABLE` statement
53pub enum NoResult {}
54
55/// Holds a `Statement` after execution of a query.Allocated
56///
57/// A executed statement may be in one of two states. Either the statement has yielded a result set
58/// or not. Keep in mind that some ODBC drivers just yield empty result sets on e.g. `INSERT`
59/// Statements
60pub enum ResultSetState<'a, 'b, S, AC: AutocommitMode> {
61    Data(Statement<'a, 'b, S, HasResult, AC>),
62    NoData(Statement<'a, 'b, S, NoResult, AC>),
63}
64pub use ResultSetState::*;
65use std::ptr::null_mut;
66use odbc_safe::AutocommitMode;
67
68/// A `Statement` can be used to execute queries and retrieves results.
69pub struct Statement<'a, 'b, S, R, AC: AutocommitMode> {
70    raii: Raii<'a, ffi::Stmt>,
71    state: PhantomData<S>,
72    autocommit_mode: PhantomData<AC>,
73    // Indicates wether there is an open result set or not associated with this statement.
74    result: PhantomData<R>,
75    parameters: PhantomData<&'b [u8]>,
76    param_ind_buffers: Chunks<ffi::SQLLEN>,
77    // encoded values are saved to use its pointer.
78    encoded_values: Vec<EncodedValue>,
79}
80
81/// Used to retrieve data from the fields of a query result
82pub struct Cursor<'s, 'a: 's, 'b: 's, S: 's, AC: AutocommitMode> {
83    stmt: &'s mut Statement<'a, 'b, S, HasResult, AC>,
84    buffer: Vec<u8>,
85}
86
87#[derive(Clone, Debug, Eq, PartialEq)]
88pub struct ColumnDescriptor {
89    pub name: String,
90    pub data_type: ffi::SqlDataType,
91    pub column_size: Option<ffi::SQLULEN>,
92    pub decimal_digits: Option<u16>,
93    pub nullable: Option<bool>,
94}
95
96impl<'a, 'b, S, R, AC: AutocommitMode> Handle for Statement<'a, 'b, S, R, AC> {
97    type To = ffi::Stmt;
98    unsafe fn handle(&self) -> ffi::SQLHSTMT {
99        self.raii.handle()
100    }
101}
102
103impl<'a, 'b, S, R, AC: AutocommitMode> Statement<'a, 'b, S, R, AC> {
104    fn with_raii(raii: Raii<'a, ffi::Stmt>) -> Self {
105        Statement {
106            raii: raii,
107            autocommit_mode: PhantomData,
108            state: PhantomData,
109            result: PhantomData,
110            parameters: PhantomData,
111            param_ind_buffers: Chunks::new(),
112            encoded_values: Vec::new(),
113        }
114    }
115}
116
117impl<'a, 'b, 'env, AC: AutocommitMode> Statement<'a, 'b, Allocated, NoResult, AC> {
118    pub fn with_parent(ds: &'a Connection<'env, AC>) -> Result<Self> {
119        let raii = Raii::with_parent(ds).into_result(ds)?;
120        Ok(Self::with_raii(raii))
121    }
122
123    pub fn affected_row_count(&self) -> Result<ffi::SQLLEN> {
124        self.raii.affected_row_count().into_result(self)
125    }
126
127    pub fn tables(self, catalog_name: &String, schema_name: &String, table_name: &String, table_type: &String) -> Result<Statement<'a, 'b, Executed, HasResult, AC>> {
128        self.tables_str(catalog_name.as_str(), schema_name.as_str(), table_name.as_str(), table_type.as_str())
129    }
130
131    pub fn tables_str(self, catalog_name: &str, schema_name: &str, table_name: &str, table_type: &str) -> Result<Statement<'a, 'b, Executed, HasResult, AC>> {
132        self.tables_opt_str(Option::Some(catalog_name), Option::Some(schema_name), Option::Some(table_name), table_type)
133    }
134
135    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>> {
136        self.raii.tables(catalog_name, schema_name, table_name, table_type).into_result(&self)?;
137        Ok(Statement::with_raii(self.raii))
138    }
139
140    /// Executes a preparable statement, using the current values of the parameter marker variables
141    /// if any parameters exist in the statement.
142    ///
143    /// `SQLExecDirect` is the fastest way to submit an SQL statement for one-time execution.
144    pub fn exec_direct(mut self, statement_text: &str) -> Result<ResultSetState<'a, 'b, Executed, AC>> {
145        if self.raii.exec_direct(statement_text).into_result(&self)? {
146            let num_cols = self.raii.num_result_cols().into_result(&self)?;
147            if num_cols > 0 {
148                Ok(ResultSetState::Data(Statement::with_raii(self.raii)))
149            } else {
150                Ok(ResultSetState::NoData(Statement::with_raii(self.raii)))
151            }
152        } else {
153            Ok(ResultSetState::NoData(Statement::with_raii(self.raii)))
154        }
155    }
156
157    /// Executes a preparable statement, using the current values of the parameter marker variables
158    /// if any parameters exist in the statement.
159    ///
160    /// `SQLExecDirect` is the fastest way to submit an SQL statement for one-time execution.
161    pub fn exec_direct_bytes(mut self, bytes: &[u8]) -> Result<ResultSetState<'a, 'b, Executed, AC>> {
162        if self.raii.exec_direct_bytes(bytes).into_result(&self)? {
163            let num_cols = self.raii.num_result_cols().into_result(&self)?;
164            if num_cols > 0 {
165                Ok(ResultSetState::Data(Statement::with_raii(self.raii)))
166            } else {
167                Ok(ResultSetState::NoData(Statement::with_raii(self.raii)))
168            }
169        } else {
170            Ok(ResultSetState::NoData(Statement::with_raii(self.raii)))
171        }
172    }
173}
174
175impl<'a, 'b, S, AC: AutocommitMode> Statement<'a, 'b, S, HasResult, AC> {
176
177    pub fn affected_row_count(&self) -> Result<ffi::SQLLEN> {
178        self.raii.affected_row_count().into_result(self)
179    }
180
181    /// The number of columns in a result set
182    ///
183    /// Can be called successfully only when the statement is in the prepared, executed, or
184    /// positioned state. If the statement does not return columns the result will be 0.
185    pub fn num_result_cols(&self) -> Result<i16> {
186        self.raii.num_result_cols().into_result(self)
187    }
188
189    /// Returns description struct for result set column with a given index. Note: indexing is starting from 1.
190    pub fn describe_col(&self, idx: u16) -> Result<ColumnDescriptor> {
191        self.raii.describe_col(idx).into_result(self)
192    }
193
194    /// Fetches the next rowset of data from the result set and returns data for all bound columns.
195    pub fn fetch<'s>(&'s mut self) -> Result<Option<Cursor<'s, 'a, 'b, S, AC>>> {
196        if self.raii.fetch().into_result(self)? {
197            Ok(Some(Cursor {
198                stmt: self,
199                buffer: vec![0; 512],
200            }))
201        } else {
202            Ok(None)
203        }
204    }
205
206    /// Call this method to reuse the statement to execute another query.
207    ///
208    /// For many drivers allocating new statements is expensive. So reusing a `Statement` is usually
209    /// more efficient than freeing an existing and allocating a new one. However to reuse a
210    /// statement any open result sets must be closed.
211    /// Only call this method if you have already read the result set returned by the previous
212    /// query, or if you do no not intend to read it.
213    ///
214    /// # Example
215    ///
216    /// ```
217    /// # use odbc::*;
218    /// # fn reuse () -> Result<()> {
219    /// let env = create_environment_v3().map_err(|e| e.unwrap())?;
220    /// let conn = env.connect("TestDataSource", "", "")?;
221    /// let stmt = Statement::with_parent(&conn)?;
222    /// let stmt = match stmt.exec_direct("CREATE TABLE STAGE (A TEXT, B TEXT);")?{
223    ///     // Some drivers will return an empty result set. We need to close it before we can use
224    ///     // statement again.
225    ///     Data(stmt) => stmt.close_cursor()?,
226    ///     NoData(stmt) => stmt,
227    /// };
228    /// let stmt = stmt.exec_direct("INSERT INTO STAGE (A, B) VALUES ('Hello', 'World');")?;
229    /// //...
230    /// # Ok(())
231    /// # };
232    /// ```
233    pub fn close_cursor(mut self) -> Result<Statement<'a, 'b, S, NoResult, AC>> {
234        self.raii.close_cursor().into_result(&self)?;
235        Ok(Statement::with_raii(self.raii))
236    }
237}
238
239impl<'a, 'b, 'c, S, AC: AutocommitMode> Cursor<'a, 'b, 'c, S, AC> {
240    /// Retrieves data for a single column in the result set
241    ///
242    /// ## Panics
243    ///
244    /// If you try to convert to `&str` but the data can't be converted
245    /// without allocating an intermediate buffer.
246    pub fn get_data<'d, T>(&'d mut self, col_or_param_num: u16) -> Result<Option<T>>
247    where
248        T: Output<'d>,
249    {
250        T::get_data(&mut self.stmt.raii, col_or_param_num, &mut self.buffer).into_result(self.stmt)
251    }
252}
253
254impl<'p> Raii<'p, ffi::Stmt> {
255    fn affected_row_count(&self) -> Return<ffi::SQLLEN> {
256        let mut count: ffi::SQLLEN = 0;
257        unsafe {
258            match ffi::SQLRowCount(self.handle(), &mut count as *mut ffi::SQLLEN) {
259                SQL_SUCCESS => Return::Success(count),
260                SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(count),
261                SQL_ERROR => Return::Error,
262                r => panic!("SQLRowCount returned unexpected result: {:?}", r),
263            }
264        }
265    }
266
267    fn num_result_cols(&self) -> Return<i16> {
268        let mut num_cols: ffi::SQLSMALLINT = 0;
269        unsafe {
270            match ffi::SQLNumResultCols(self.handle(), &mut num_cols as *mut ffi::SQLSMALLINT) {
271                SQL_SUCCESS => Return::Success(num_cols),
272                SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(num_cols),
273                SQL_ERROR => Return::Error,
274                r => panic!("SQLNumResultCols returned unexpected result: {:?}", r),
275            }
276        }
277    }
278
279    fn describe_col(&self, idx: u16) -> Return<ColumnDescriptor> {
280        let mut name_buffer: [u8; 512] = [0; 512];
281        let mut name_length: ffi::SQLSMALLINT = 0;
282        let mut data_type: ffi::SqlDataType = ffi::SqlDataType::SQL_UNKNOWN_TYPE;
283        let mut column_size: ffi::SQLULEN = 0;
284        let mut decimal_digits: ffi::SQLSMALLINT = 0;
285        let mut nullable: Nullable = Nullable::SQL_NULLABLE_UNKNOWN;
286        unsafe {
287            match ffi::SQLDescribeCol(
288                self.handle(),
289                idx,
290                name_buffer.as_mut_ptr(),
291                name_buffer.len() as ffi::SQLSMALLINT,
292                &mut name_length as *mut ffi::SQLSMALLINT,
293                &mut data_type as *mut ffi::SqlDataType,
294                &mut column_size as *mut ffi::SQLULEN,
295                &mut decimal_digits as *mut ffi::SQLSMALLINT,
296                &mut nullable as *mut ffi::Nullable,
297            ) {
298                SQL_SUCCESS => Return::Success(ColumnDescriptor {
299                    name: ::environment::DB_ENCODING.decode(&name_buffer[..(name_length as usize)]).0
300                        .to_string(),
301                    data_type: data_type,
302                    column_size: if column_size == 0 {
303                        None
304                    } else {
305                        Some(column_size)
306                    },
307                    decimal_digits: if decimal_digits == 0 {
308                        None
309                    } else {
310                        Some(decimal_digits as u16)
311                    },
312                    nullable: match nullable {
313                        Nullable::SQL_NULLABLE_UNKNOWN => None,
314                        Nullable::SQL_NULLABLE => Some(true),
315                        Nullable::SQL_NO_NULLS => Some(false),
316                    },
317                }),
318                SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(ColumnDescriptor {
319                    name: ::environment::DB_ENCODING.decode(&name_buffer[..(name_length as usize)]).0
320                        .to_string(),
321                    data_type: data_type,
322                    column_size: if column_size == 0 {
323                        None
324                    } else {
325                        Some(column_size)
326                    },
327                    decimal_digits: if decimal_digits == 0 {
328                        None
329                    } else {
330                        Some(decimal_digits as u16)
331                    },
332                    nullable: match nullable {
333                        Nullable::SQL_NULLABLE_UNKNOWN => None,
334                        Nullable::SQL_NULLABLE => Some(true),
335                        Nullable::SQL_NO_NULLS => Some(false),
336                    },
337                }),
338                SQL_ERROR => Return::Error,
339                r => panic!("SQLDescribeCol returned unexpected result: {:?}", r),
340            }
341        }
342
343    }
344
345    fn exec_direct(&mut self, statement_text: &str) -> Return<bool> {
346        let bytes = unsafe { crate::environment::DB_ENCODING }.encode(statement_text).0;
347
348        let length = bytes.len();
349        if length > ffi::SQLINTEGER::max_value() as usize {
350            panic!("Statement text too long");
351        }
352        match unsafe {
353            ffi::SQLExecDirect(
354                self.handle(),
355                bytes.as_ptr(),
356                length as ffi::SQLINTEGER,
357            )
358        } {
359            ffi::SQL_SUCCESS => Return::Success(true),
360            ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(true),
361            ffi::SQL_ERROR => Return::Error,
362            ffi::SQL_NEED_DATA => panic!("SQLExecDirec returned SQL_NEED_DATA"),
363            ffi::SQL_NO_DATA => Return::Success(false),
364            r => panic!("SQLExecDirect returned unexpected result: {:?}", r),
365        }
366    }
367
368    fn exec_direct_bytes(&mut self, bytes: &[u8]) -> Return<bool> {
369        let length = bytes.len();
370        if length > ffi::SQLINTEGER::max_value() as usize {
371            panic!("Statement text too long");
372        }
373        match unsafe {
374            ffi::SQLExecDirect(
375                self.handle(),
376                bytes.as_ptr(),
377                length as ffi::SQLINTEGER,
378            )
379        } {
380            ffi::SQL_SUCCESS => Return::Success(true),
381            ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(true),
382            ffi::SQL_ERROR => Return::Error,
383            ffi::SQL_NEED_DATA => panic!("SQLExecDirec returned SQL_NEED_DATA"),
384            ffi::SQL_NO_DATA => Return::Success(false),
385            r => panic!("SQLExecDirect returned unexpected result: {:?}", r),
386        }
387    }
388
389    /// Fetches the next rowset of data from the result set and returns data for all bound columns.
390    fn fetch(&mut self) -> Return<bool> {
391        match unsafe { ffi::SQLFetch(self.handle()) } {
392            ffi::SQL_SUCCESS => Return::Success(true),
393            ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(true),
394            ffi::SQL_ERROR => Return::Error,
395            ffi::SQL_NO_DATA => Return::Success(false),
396            r => panic!("SQLFetch returned unexpected result: {:?}", r),
397        }
398    }
399
400    fn tables(&mut self, catalog_name: Option<&str>, schema_name: Option<&str>, table_name: Option<&str>, table_type: &str) -> Return<()> {
401        unsafe {
402            let mut catalog: *const odbc_sys::SQLCHAR = null_mut();
403            let mut schema: *const odbc_sys::SQLCHAR = null_mut();
404            let mut table: *const odbc_sys::SQLCHAR = null_mut();
405
406            let mut catalog_size: odbc_sys::SQLSMALLINT = 0;
407            let mut schema_size: odbc_sys::SQLSMALLINT = 0;
408            let mut table_size: odbc_sys::SQLSMALLINT = 0;
409
410            if catalog_name.is_some() {
411                catalog = catalog_name.unwrap().as_ptr();
412                catalog_size = catalog_name.unwrap().len() as odbc_sys::SQLSMALLINT;
413            }
414
415            if schema_name.is_some() {
416                schema = schema_name.unwrap().as_ptr();
417                schema_size = schema_name.unwrap().len() as odbc_sys::SQLSMALLINT;
418            }
419
420            if table_name.is_some() {
421                table = table_name.unwrap().as_ptr();
422                table_size = table_name.unwrap().len() as odbc_sys::SQLSMALLINT;
423            }
424
425            match odbc_sys::SQLTables(
426                self.handle(),
427                catalog,
428                catalog_size,
429                schema,
430                schema_size,
431                table,
432                table_size,
433                table_type.as_ptr(),
434                table_type.as_bytes().len() as odbc_sys::SQLSMALLINT,
435            ) {
436                SQL_SUCCESS => Return::Success(()),
437                SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(()),
438                SQL_ERROR => Return::Error,
439                r => panic!("SQLTables returned: {:?}", r),
440            }
441        }
442    }
443
444    fn close_cursor(&mut self) -> Return<()> {
445        unsafe {
446            match ffi::SQLCloseCursor(self.handle()) {
447                ffi::SQL_SUCCESS => Return::Success(()),
448                ffi::SQL_SUCCESS_WITH_INFO => Return::SuccessWithInfo(()),
449                ffi::SQL_ERROR => Return::Error,
450                r => panic!("unexpected return value from SQLCloseCursor: {:?}", r),
451            }
452        }
453    }
454}
455
456unsafe impl<'con, 'param, C, P, AC: AutocommitMode> safe::Handle for Statement<'con, 'param, C, P, AC> {
457
458    const HANDLE_TYPE : ffi::HandleType = ffi::SQL_HANDLE_STMT;
459
460    fn handle(&self) -> ffi::SQLHANDLE {
461        <Raii<ffi::Stmt> as safe::Handle>::handle(&self.raii)
462    }
463}