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<StatementMetadata> {
80 use crate::result::Error::DeserializationError;
81
82 let result_ptr = unsafe { ffi::mysql_stmt_result_metadata(self.stmt.as_ptr()) };
83 self.did_an_error_occur()?;
84 NonNull::new(result_ptr)
85 .map(StatementMetadata::new)
86 .ok_or_else(|| DeserializationError("No metadata exists".into()))
87 }
88
89 pub(super) fn did_an_error_occur(&self) -> QueryResult<()> {
90 use crate::result::Error::DatabaseError;
91
92 let error_message = self.last_error_message();
93 if error_message.is_empty() {
94 Ok(())
95 } else {
96 Err(DatabaseError(
97 self.last_error_type(),
98 Box::new(error_message),
99 ))
100 }
101 }
102
103 fn last_error_type(&self) -> DatabaseErrorKind {
104 let last_error_number = unsafe { ffi::mysql_stmt_errno(self.stmt.as_ptr()) };
105
106 DB::map_error_number(last_error_number)
107 }
108
109 pub unsafe fn bind_result(&self, binds: *mut ffi::MYSQL_BIND) -> QueryResult<()> {
112 unsafe {
113 ffi::mysql_stmt_bind_result(self.stmt.as_ptr(), binds);
114 }
115 self.did_an_error_occur()
116 }
117}
118
119impl<'a, DB: MysqlLikeBackend> MaybeCached<'a, Statement<DB>> {
120 pub(super) fn execute_statement(
121 self,
122 binds: &mut OutputBinds,
123 ) -> QueryResult<StatementUse<'a, DB>> {
124 unsafe {
125 binds.with_mysql_binds(|bind_ptr| self.bind_result(bind_ptr))?;
126 self.execute()
127 }
128 }
129
130 pub(super) unsafe fn execute(self) -> QueryResult<StatementUse<'a, DB>> {
134 unsafe {
135 ffi::mysql_stmt_execute(self.stmt.as_ptr());
136 }
137 self.did_an_error_occur()?;
138 unsafe {
139 ffi::mysql_stmt_store_result(self.stmt.as_ptr());
140 }
141 let ret = StatementUse { inner: self };
142 ret.inner.did_an_error_occur()?;
143 Ok(ret)
144 }
145}
146
147impl<DB: MysqlLikeBackend> Drop for Statement<DB> {
148 fn drop(&mut self) {
149 unsafe { ffi::mysql_stmt_close(self.stmt.as_ptr()) };
150 }
151}
152
153#[allow(missing_debug_implementations)]
154pub(super) struct StatementUse<'a, DB: MysqlLikeBackend> {
155 inner: MaybeCached<'a, Statement<DB>>,
156}
157
158impl<DB: MysqlLikeBackend> StatementUse<'_, DB> {
159 pub(in crate::mysql_like::connection) fn affected_rows(&self) -> QueryResult<usize> {
160 let affected_rows = unsafe { ffi::mysql_stmt_affected_rows(self.inner.stmt.as_ptr()) };
161 affected_rows
162 .try_into()
163 .map_err(|e| Error::DeserializationError(Box::new(e)))
164 }
165
166 pub(in crate::mysql_like::connection) unsafe fn result_size(&mut self) -> QueryResult<usize> {
169 let size = unsafe { ffi::mysql_stmt_num_rows(self.inner.stmt.as_ptr()) };
170 usize::try_from(size).map_err(|e| Error::DeserializationError(Box::new(e)))
171 }
172
173 pub(super) fn populate_row_buffers(&self, binds: &mut OutputBinds) -> QueryResult<Option<()>> {
174 if binds.are_invalid() {
176 binds.bind_results(self)?;
177 }
178 let next_row_result = unsafe { ffi::mysql_stmt_fetch(self.inner.stmt.as_ptr()) };
179 if next_row_result < 0 {
180 self.inner.did_an_error_occur().map(Some)
181 } else {
182 #[allow(clippy::cast_sign_loss)] match next_row_result as libc::c_uint {
184 ffi::MYSQL_NO_DATA => Ok(None),
185 ffi::MYSQL_DATA_TRUNCATED => binds.populate_dynamic_buffers(self).map(Some),
186 0 => {
187 binds.update_buffer_lengths();
188 Ok(Some(()))
189 }
190 _error => self.inner.did_an_error_occur().map(Some),
191 }
192 }
193 }
194
195 pub(in crate::mysql_like::connection) unsafe fn fetch_column(
196 &self,
197 bind: &mut ffi::MYSQL_BIND,
198 idx: usize,
199 offset: usize,
200 ) -> QueryResult<()> {
201 unsafe {
202 ffi::mysql_stmt_fetch_column(
203 self.inner.stmt.as_ptr(),
204 bind,
205 idx.try_into()
206 .map_err(|e| Error::DeserializationError(Box::new(e)))?,
207 offset as libc::c_ulong,
208 );
209 }
210 self.inner.did_an_error_occur()
211 }
212
213 pub(in crate::mysql_like::connection) unsafe fn bind_result(
216 &self,
217 binds: *mut ffi::MYSQL_BIND,
218 ) -> QueryResult<()> {
219 unsafe { self.inner.bind_result(binds) }
220 }
221}
222
223impl<DB: MysqlLikeBackend> Drop for StatementUse<'_, DB> {
224 fn drop(&mut self) {
225 unsafe {
226 ffi::mysql_stmt_free_result(self.inner.stmt.as_ptr());
227 }
228 }
229}