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