Skip to main content

odbc_safe/
statement.rs

1use super::*;
2use sys::*;
3use std::marker::PhantomData;
4
5/// A `Statement` is most easily thought of as an SQL statement, such as
6/// `SELECT * FROM Employee`.
7///
8/// * The statement's state
9/// * The current statement-level diagnostics
10/// * The addresses of the application variables bound to the statement's
11/// parameters and result set
12///   columns
13/// * The current settings of each statement attribute
14///
15/// See [Statement Handles][1]
16///
17/// Specific to the rust wrapper of an ODBC Statement is, that we do keep track
18/// of the lifetimes of
19/// the parent `Connection`, parameters as well as `columns` bound to the
20/// `Statement`. Since it is
21/// possible to unbind the parameters and columns we have to keep track of
22/// their lifetimes
23/// seperatly.
24///
25/// [1]: https://docs.microsoft.com/sql/odbc/reference/develop-app/statement-handles
26#[derive(Debug)]
27pub struct Statement<'con, 'param, 'col, C = NoCursor, A = Unprepared> {
28    cursor: PhantomData<C>,
29    access_plan: PhantomData<A>,
30    /// Statement may not outlive parameters bound to it.
31    parameters: PhantomData<&'param [u8]>,
32    columns: PhantomData<&'col [u8]>,
33    handle: HStmt<'con>,
34}
35
36/// Cursor state of `Statement`. A statement is likely to enter this state
37/// after executing e.g a
38/// `SELECT` query.
39#[derive(Debug)]
40#[allow(missing_copy_implementations)]
41pub enum Open {}
42/// State used by `Statement`. A statement is likely to enter this state after
43/// executing e.g. a
44/// `CREATE TABLE` statement.
45#[derive(Debug)]
46#[allow(missing_copy_implementations)]
47pub enum NoCursor {}
48/// Cursor state of `Statement`. A statement will enter this state after a
49/// successful call to
50/// `fetch()`
51#[derive(Debug)]
52#[allow(missing_copy_implementations)]
53pub enum Positioned {}
54/// State used by `Statement`. A statement will enter this state after a
55/// successful call to
56/// `prepare()`.
57#[derive(Debug)]
58#[allow(missing_copy_implementations)]
59pub enum Prepared {}
60/// State used by `Statement`. Indicates that no Access Plan has been created,
61/// yet.
62#[derive(Debug)]
63#[allow(missing_copy_implementations)]
64pub enum Unprepared {}
65
66/// Implemented by the `Open` and `Positioned` states for `Statement`.
67pub trait CursorState {}
68impl CursorState for Open {}
69impl CursorState for Positioned {}
70
71impl<'con, 'param, 'col, S, A> Statement<'con, 'param, 'col, S, A> {
72    /// Provides access to the raw ODBC Statement Handle
73    pub fn as_raw(&self) -> SQLHSTMT {
74        self.handle.as_raw()
75    }
76
77    /// Binds a parameter to a parameter marker in an SQL Statement
78    ///
79    /// # Result
80    /// This method will destroy the statement and create a new one which may not outlive the bound
81    /// parameter. This is to ensure that the statement will not derefernce an invalid pointer
82    /// during execution. Use `reset_parameters` to reset the bound parameters and increase the
83    /// `'param` lifetime back to `'static`.
84    ///
85    /// # Arguments
86    /// * `parameter_number` - Index of the marker to bind to the parameter. Starting at `1`
87    /// * `parameter_type` - SQL Type of the parameter
88    /// * `value` - Reference to bind to the marker
89    ///
90    /// See [SQLBindParameter Function][1]
91    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlbindparameter-function#columnsize-argument
92    pub fn bind_input_parameter<'p, T>(
93        mut self,
94        parameter_number: SQLUSMALLINT,
95        parameter_type: DataType,
96        value: &'p T,
97        indicator: Option<&'p SQLLEN>,
98    ) -> Return<Statement<'con, 'p, 'col, S, A>, Self>
99    where
100        T: CDataType + ?Sized,
101        'param: 'p,
102    {
103        unsafe {
104            match self.handle.bind_input_parameter(
105                parameter_number,
106                parameter_type,
107                value,
108                indicator,
109            ) {
110                Success(()) => Success(self.transit()),
111                Info(()) => Info(self.transit()),
112                Error(()) => Error(self.transit()),
113            }
114        }
115    }
116
117    /// Binds a buffer and an indicator to a column.
118    ///
119    /// See [SQLBindCol][1]:
120    /// [1]: [https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlbindcol-function]
121    pub fn bind_col<'col_new, T>(
122        mut self,
123        column_number: SQLUSMALLINT,
124        value: &'col_new mut T,
125        indicator: Option<&'col_new mut SQLLEN>,
126    ) -> Return<Statement<'con, 'param, 'col_new, S, A>, Self>
127    where
128        T: CDataType + ?Sized,
129        'col: 'col_new,
130    {
131        unsafe {
132            match self.handle.bind_col(column_number, value, indicator) {
133                Success(()) => Success(self.transit()),
134                Info(()) => Info(self.transit()),
135                Error(()) => Error(self.transit()),
136            }
137        }
138    }
139
140    /// Unbinds the parameters from the parameter markers
141    pub fn reset_parameters(mut self) -> Statement<'con, 'static, 'col, S, A> {
142        self.handle.reset_parameters().unwrap();
143        self.transit()
144    }
145
146    /// Unbinds column buffers from result set.
147    pub fn reset_columns(mut self) -> Statement<'con, 'param, 'static, S, A> {
148        self.handle.reset_columns().unwrap();
149        self.transit()
150    }
151
152    fn transit<'p, 'c, S2, A2>(self) -> Statement<'con, 'p, 'c, S2, A2> {
153        Statement {
154            handle: self.handle,
155            parameters: PhantomData,
156            columns: PhantomData,
157            cursor: PhantomData,
158            access_plan: PhantomData,
159        }
160    }
161}
162
163impl<'con, 'param, 'col, C, A> Statement<'con, 'param, 'col, C, A>
164where
165    C: CursorState,
166{
167    /// Returns the number of columns of the result set
168    ///
169    /// See [SQLNumResultCols][1]
170    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlnumresultcols-function
171    pub fn num_result_cols(&self) -> Return<SQLSMALLINT> {
172        self.handle.num_result_cols()
173    }
174
175    /// Returns the number of rows affetced by INSERT, UPDATE, etc
176    ///
177    /// See [SQLRowCount][1]
178    /// [1]: https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlrowcount-function
179    pub fn affected_row_count(&self) -> Return<SQLLEN> { self.handle.affected_row_count() }
180
181    /// Advances Cursor to next row
182    ///
183    /// See [SQLFetch][1]
184    /// See [Fetching a Row of Data][2]
185    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlfetch-function
186    /// [2]: https://docs.microsoft.com/sql/odbc/reference/develop-app/fetching-a-row-of-data
187    pub fn fetch(
188        mut self,
189    ) -> ReturnOption<
190        Statement<'con, 'param, 'col, Positioned, A>,
191        Statement<'con, 'param, 'col, NoCursor, A>,
192    > {
193        match self.handle.fetch() {
194            ReturnOption::Success(()) => ReturnOption::Success(self.transit()),
195            ReturnOption::Info(()) => ReturnOption::Info(self.transit()),
196            ReturnOption::NoData(()) => ReturnOption::NoData(self.transit()),
197            ReturnOption::Error(()) => ReturnOption::Error(self.transit()),
198        }
199    }
200
201    /// Closes the cursor. Cursors only need to be closed explicitly if the
202    /// Statement handle is
203    /// intended to be reused, but a result set is not consumed.
204    ///
205    /// See [SQLCloseCursor][1]
206    /// See [Closing the Cursor][2]
207    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlclosecursor-function
208    /// [2]: https://docs.microsoft.com/sql/odbc/reference/develop-app/closing-the-cursor
209    pub fn close_cursor(
210        mut self,
211    ) -> Return<Statement<'con, 'param, 'col, NoCursor>, Statement<'con, 'param, 'col, C, A>> {
212        match self.handle.close_cursor() {
213            Success(()) => Success(self.transit()),
214            Info(()) => Info(self.transit()),
215            Error(()) => Error(self.transit()),
216        }
217    }
218
219    /// Return information about result set column
220    ///
221    /// See [SQLDescribeCol Function][1]
222    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqldescribecol-function
223    pub fn describe_col<T>(
224        &mut self,
225        column_number: SQLUSMALLINT,
226        column_name: &mut T,
227        column_name_indicator: &mut SQLSMALLINT,
228        nullable: &mut Nullable,
229    ) -> Return<Option<DataType>>
230    where
231        T: OutputBuffer + ?Sized,
232    {
233        let mut data_type = SQL_UNKNOWN_TYPE;
234        let mut column_size = 0;
235        let mut decimal_digits = 0;
236        self.handle.describe_col(
237            column_number,
238            column_name,
239            column_name_indicator,
240            &mut data_type,
241            &mut column_size,
242            &mut decimal_digits,
243            nullable
244        ).map(|()| DataType::new(data_type, column_size, decimal_digits))
245    }
246}
247
248impl<'con, 'param, 'col> Statement<'con, 'param, 'col, NoCursor, Unprepared> {
249    /// Allocates a new `Statement`
250    pub fn with_parent<AC: AutocommitMode>(parent: &'con Connection<AC>) -> Return<Self> {
251        HStmt::allocate(parent.as_hdbc()).map(|handle| {
252            Statement {
253                handle,
254                parameters: PhantomData,
255                cursor: PhantomData,
256                columns: PhantomData,
257                access_plan: PhantomData,
258            }
259        })
260    }
261
262    /// Prepares a `Statement` for execution by creating an Access Plan.
263    ///
264    /// See [SQLPrepare Function][1]
265    /// See [Prepare and Execute a Statement (ODBC)][2]
266    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlprepare-function
267    /// [2]: https://docs.microsoft.com/sql/relational-databases/native-client-odbc-how-to/execute-queries/prepare-and-execute-a-statement-odbc
268    pub fn prepare<T>(
269        mut self,
270        statement_text: &T,
271    ) -> Return<
272        Statement<'con, 'param, 'col, NoCursor, Prepared>,
273        Statement<'con, 'param, 'col, NoCursor>,
274    >
275    where
276        T: SqlStr + ?Sized,
277    {
278        // According to the state transition table preparing statements which are
279        // already prepared
280        // is possible. However we would need to check the status code in order to
281        // decide which
282        // state the `Statement` is in the case of an error. So for now we only support
283        // preparing
284        // freshly allocated statements until someone has a use case for 'repreparing'
285        // a statement.
286        match self.handle.prepare(statement_text) {
287            Success(()) => Success(self.transit()),
288            Info(()) => Info(self.transit()),
289            Error(()) => Error(self.transit()),
290        }
291    }
292
293    /// Executes a preparable statement, using the current values of the
294    /// parametr marker variables.
295    ///
296    /// * See [SQLExecDirect][1]
297    /// * See [Direct Execution][2]
298    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlexecdirect-function
299    /// [2]: https://docs.microsoft.com/sql/odbc/reference/develop-app/direct-execution-odbc
300    pub fn exec_direct<T>(
301        mut self,
302        statement_text: &T,
303    ) -> ReturnOption<
304        ResultSet<'con, 'param, 'col, Unprepared>,
305        Statement<'con, 'param, 'col, NoCursor>,
306    >
307    where
308        T: SqlStr + ?Sized,
309    {
310        match self.handle.exec_direct(statement_text) {
311            ReturnOption::Success(()) => ReturnOption::Success(self.transit()),
312            ReturnOption::Info(()) => ReturnOption::Info(self.transit()),
313            ReturnOption::NoData(()) => ReturnOption::NoData(self.transit()),
314            ReturnOption::Error(()) => ReturnOption::Error(self.transit()),
315        }
316    }
317}
318
319impl<'con, 'param, 'col> Statement<'con, 'param, 'col, NoCursor, Prepared> {
320    /// Return information about result set column
321    ///
322    /// See [SQLDescribeCol Function][1]
323    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqldescribecol-function
324    pub fn describe_col<T>(
325        &mut self,
326        column_number: SQLUSMALLINT,
327        column_name: &mut T,
328        column_name_indicator: &mut SQLSMALLINT,
329        nullable: &mut Nullable,
330    ) -> Return<Option<DataType>>
331    where
332        T: OutputBuffer + ?Sized,
333    {
334        let mut data_type = SQL_UNKNOWN_TYPE;
335        let mut column_size = 0;
336        let mut decimal_digits = 0;
337        self.handle.describe_col(
338            column_number,
339            column_name,
340            column_name_indicator,
341            &mut data_type,
342            &mut column_size,
343            &mut decimal_digits,
344            nullable
345        ).map(|()| DataType::new(data_type, column_size, decimal_digits))
346    }
347
348    /// Executes a prepared statement, using the current values fo the
349    /// parameter marker variables
350    /// if any parameter markers exist in the statement.
351    ///
352    /// See [SQLExecute Function][1]
353    /// See [Prepared Execution][2]
354    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlexecute-function
355    /// [2]: https://docs.microsoft.com/sql/odbc/reference/develop-app/prepared-execution-odbc
356    pub fn execute(mut self) -> ReturnOption<ResultSet<'con, 'param, 'col, Prepared>, Self> {
357        match self.handle.execute() {
358            ReturnOption::Success(()) => ReturnOption::Success(self.transit()),
359            ReturnOption::Info(()) => ReturnOption::Info(self.transit()),
360            ReturnOption::Error(()) => ReturnOption::Error(self.transit()),
361            ReturnOption::NoData(()) => ReturnOption::NoData(self.transit()),
362        }
363    }
364}
365
366impl<'con, 'param, 'col, A> Statement<'con, 'param, 'col, Positioned, A> {
367    /// Retrieves data for a single column or output parameter.
368    ///
369    /// See [SQLGetData][1]
370    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlgetdata-function
371    pub fn get_data<T>(
372        &mut self,
373        col_or_param_num: SQLUSMALLINT,
374        target: &mut T,
375    ) -> ReturnOption<Indicator>
376    where
377        T: CDataType + ?Sized,
378    {
379        self.handle.get_data(col_or_param_num, target)
380    }
381}
382
383impl<'con, 'param, 'col, C, A> Diagnostics for Statement<'con, 'param, 'col, C, A> {
384    fn diagnostics(
385        &self,
386        rec_number: SQLSMALLINT,
387        message_text: &mut [SQLCHAR],
388    ) -> ReturnOption<DiagResult> {
389        self.handle.diagnostics(rec_number, message_text)
390    }
391}