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