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}