diesel/mysql_like/connection/stmt/
mod.rs1#![allow(unsafe_code)] use core::ffi as libc;
3use core::ffi::CStr;
4use core::marker::PhantomData;
5use core::ptr::NonNull;
6use mysqlclient_sys as ffi;
7
8use super::bind::{OutputBinds, PreparedStatementBinds};
9use super::error_information::{MysqlLikeErrorInformation, sqlstate_from_ptr};
10use crate::connection::statement_cache::MaybeCached;
11use crate::mysql_like::{MysqlLikeBackend, MysqlType};
12use crate::result::{DatabaseErrorKind, Error, QueryResult, SqlState};
13
14pub(super) mod iterator;
15mod metadata;
16
17pub(super) use self::metadata::{MysqlFieldMetadata, StatementMetadata};
18
19#[allow(dead_code, missing_debug_implementations)]
20pub struct Statement<DB: MysqlLikeBackend> {
22 stmt: NonNull<ffi::MYSQL_STMT>,
23 input_binds: Option<PreparedStatementBinds>,
24 _phantom: PhantomData<DB>,
25}
26
27#[allow(unsafe_code)]
29unsafe impl<DB: MysqlLikeBackend> Send for Statement<DB> {}
30
31impl<DB: MysqlLikeBackend> Statement<DB> {
32 pub(crate) fn new(stmt: NonNull<ffi::MYSQL_STMT>) -> Self {
33 Statement {
34 stmt,
35 input_binds: None,
36 _phantom: PhantomData,
37 }
38 }
39
40 pub fn prepare(&self, query: &str) -> QueryResult<()> {
41 unsafe {
42 ffi::mysql_stmt_prepare(
43 self.stmt.as_ptr(),
44 query.as_ptr() as *const libc::c_char,
45 query.len() as libc::c_ulong,
46 );
47 }
48 self.did_an_error_occur()
49 }
50
51 pub fn bind<Iter>(&mut self, binds: Iter) -> QueryResult<()>
52 where
53 Iter: IntoIterator<Item = (MysqlType, Option<Vec<u8>>)>,
54 {
55 let input_binds = PreparedStatementBinds::from_input_data(binds);
56 self.input_bind(input_binds)
57 }
58
59 pub(super) fn input_bind(
60 &mut self,
61 mut input_binds: PreparedStatementBinds,
62 ) -> QueryResult<()> {
63 input_binds.with_mysql_binds(|bind_ptr| {
64 unsafe {
67 ffi::mysql_stmt_bind_param(self.stmt.as_ptr(), bind_ptr);
68 }
69 });
70 self.input_binds = Some(input_binds);
71 self.did_an_error_occur()
72 }
73
74 fn last_error_message(&self) -> String {
75 unsafe { CStr::from_ptr(ffi::mysql_stmt_error(self.stmt.as_ptr())) }
76 .to_string_lossy()
77 .into_owned()
78 }
79
80 fn last_error_sqlstate(&self) -> Option<SqlState> {
81 let sqlstate = unsafe { ffi::mysql_stmt_sqlstate(self.stmt.as_ptr()) };
83 unsafe { sqlstate_from_ptr(sqlstate) }
86 }
87
88 pub(super) fn metadata(&self) -> QueryResult<Option<StatementMetadata>> {
90 let result_ptr = unsafe { ffi::mysql_stmt_result_metadata(self.stmt.as_ptr()) };
91 self.did_an_error_occur()?;
92 Ok(NonNull::new(result_ptr).map(StatementMetadata::new))
93 }
94
95 pub(super) fn did_an_error_occur(&self) -> QueryResult<()> {
96 use crate::result::Error::DatabaseError;
97
98 let error_message = self.last_error_message();
99 if error_message.is_empty() {
100 Ok(())
101 } else {
102 Err(DatabaseError(
103 self.last_error_type(),
104 Box::new(MysqlLikeErrorInformation {
105 message: error_message,
106 sqlstate: self.last_error_sqlstate(),
107 }),
108 ))
109 }
110 }
111
112 fn last_error_type(&self) -> DatabaseErrorKind {
113 let last_error_number = unsafe { ffi::mysql_stmt_errno(self.stmt.as_ptr()) };
114
115 DB::map_error_number(last_error_number)
116 }
117
118 pub unsafe fn bind_result(&self, binds: *mut ffi::MYSQL_BIND) -> QueryResult<()> {
121 unsafe {
122 ffi::mysql_stmt_bind_result(self.stmt.as_ptr(), binds);
123 }
124 self.did_an_error_occur()
125 }
126}
127
128impl<'a, DB: MysqlLikeBackend> MaybeCached<'a, Statement<DB>> {
129 pub(super) fn execute_statement(
130 self,
131 binds: &mut OutputBinds,
132 ) -> QueryResult<StatementUse<'a, DB>> {
133 unsafe {
134 binds.with_mysql_binds(|bind_ptr| self.bind_result(bind_ptr))?;
135 self.execute()
136 }
137 }
138
139 pub(super) unsafe fn execute(self) -> QueryResult<StatementUse<'a, DB>> {
143 unsafe {
144 ffi::mysql_stmt_execute(self.stmt.as_ptr());
145 }
146 self.did_an_error_occur()?;
147 unsafe {
148 ffi::mysql_stmt_store_result(self.stmt.as_ptr());
149 }
150 let ret = StatementUse { inner: self };
151 ret.inner.did_an_error_occur()?;
152 Ok(ret)
153 }
154}
155
156impl<DB: MysqlLikeBackend> Drop for Statement<DB> {
157 fn drop(&mut self) {
158 unsafe { ffi::mysql_stmt_close(self.stmt.as_ptr()) };
159 }
160}
161
162#[allow(missing_debug_implementations)]
163pub(super) struct StatementUse<'a, DB: MysqlLikeBackend> {
164 inner: MaybeCached<'a, Statement<DB>>,
165}
166
167impl<DB: MysqlLikeBackend> StatementUse<'_, DB> {
168 pub(in crate::mysql_like::connection) fn affected_rows(&self) -> QueryResult<usize> {
169 let affected_rows = unsafe { ffi::mysql_stmt_affected_rows(self.inner.stmt.as_ptr()) };
170 affected_rows
171 .try_into()
172 .map_err(|e| Error::DeserializationError(Box::new(e)))
173 }
174
175 pub(in crate::mysql_like::connection) fn insert_id(&self) -> u64 {
182 unsafe { ffi::mysql_stmt_insert_id(self.inner.stmt.as_ptr()) }
185 }
186
187 pub(in crate::mysql_like::connection) unsafe fn result_size(&mut self) -> QueryResult<usize> {
190 let size = unsafe { ffi::mysql_stmt_num_rows(self.inner.stmt.as_ptr()) };
191 usize::try_from(size).map_err(|e| Error::DeserializationError(Box::new(e)))
192 }
193
194 pub(super) fn populate_row_buffers(&self, binds: &mut OutputBinds) -> QueryResult<Option<()>> {
195 if binds.are_invalid() {
197 binds.bind_results(self)?;
198 }
199 let next_row_result = unsafe { ffi::mysql_stmt_fetch(self.inner.stmt.as_ptr()) };
200 if next_row_result < 0 {
201 self.inner.did_an_error_occur().map(Some)
202 } else {
203 #[allow(clippy::cast_sign_loss)] match next_row_result as libc::c_uint {
205 ffi::MYSQL_NO_DATA => Ok(None),
206 ffi::MYSQL_DATA_TRUNCATED => binds.populate_dynamic_buffers(self).map(Some),
207 0 => {
208 binds.update_buffer_lengths();
209 Ok(Some(()))
210 }
211 _error => self.inner.did_an_error_occur().map(Some),
212 }
213 }
214 }
215
216 pub(in crate::mysql_like::connection) unsafe fn fetch_column(
217 &self,
218 bind: &mut ffi::MYSQL_BIND,
219 idx: usize,
220 offset: usize,
221 ) -> QueryResult<()> {
222 unsafe {
223 ffi::mysql_stmt_fetch_column(
224 self.inner.stmt.as_ptr(),
225 bind,
226 idx.try_into()
227 .map_err(|e| Error::DeserializationError(Box::new(e)))?,
228 offset as libc::c_ulong,
229 );
230 }
231 self.inner.did_an_error_occur()
232 }
233
234 pub(in crate::mysql_like::connection) unsafe fn bind_result(
237 &self,
238 binds: *mut ffi::MYSQL_BIND,
239 ) -> QueryResult<()> {
240 unsafe { self.inner.bind_result(binds) }
241 }
242
243 pub(super) fn metadata(&self) -> QueryResult<StatementMetadata> {
244 use crate::result::Error::DeserializationError;
245
246 self.inner
248 .metadata()?
249 .ok_or_else(|| DeserializationError("No metadata exists".into()))
250 }
251}
252
253impl<DB: MysqlLikeBackend> Drop for StatementUse<'_, DB> {
254 fn drop(&mut self) {
255 unsafe {
256 ffi::mysql_stmt_free_result(self.inner.stmt.as_ptr());
257 }
258 }
259}