diesel/mysql/connection/stmt/
mod.rs1#![allow(unsafe_code)] use mysqlclient_sys as ffi;
3use std::ffi::CStr;
4use std::os::raw as libc;
5use std::ptr::NonNull;
6
7use super::bind::{OutputBinds, PreparedStatementBinds};
8use crate::connection::statement_cache::MaybeCached;
9use crate::mysql::MysqlType;
10use crate::result::{DatabaseErrorKind, Error, QueryResult};
11
12pub(super) mod iterator;
13mod metadata;
14
15pub(super) use self::metadata::{MysqlFieldMetadata, StatementMetadata};
16
17#[allow(dead_code, missing_debug_implementations)]
18pub struct Statement {
20 stmt: NonNull<ffi::MYSQL_STMT>,
21 input_binds: Option<PreparedStatementBinds>,
22}
23
24#[allow(unsafe_code)]
26unsafe impl Send for Statement {}
27
28impl Statement {
29 pub(crate) fn new(stmt: NonNull<ffi::MYSQL_STMT>) -> Self {
30 Statement {
31 stmt,
32 input_binds: None,
33 }
34 }
35
36 pub fn prepare(&self, query: &str) -> QueryResult<()> {
37 unsafe {
38 ffi::mysql_stmt_prepare(
39 self.stmt.as_ptr(),
40 query.as_ptr() as *const libc::c_char,
41 query.len() as libc::c_ulong,
42 );
43 }
44 self.did_an_error_occur()
45 }
46
47 pub fn bind<Iter>(&mut self, binds: Iter) -> QueryResult<()>
48 where
49 Iter: IntoIterator<Item = (MysqlType, Option<Vec<u8>>)>,
50 {
51 let input_binds = PreparedStatementBinds::from_input_data(binds);
52 self.input_bind(input_binds)
53 }
54
55 pub(super) fn input_bind(
56 &mut self,
57 mut input_binds: PreparedStatementBinds,
58 ) -> QueryResult<()> {
59 input_binds.with_mysql_binds(|bind_ptr| {
60 unsafe {
63 ffi::mysql_stmt_bind_param(self.stmt.as_ptr(), bind_ptr);
64 }
65 });
66 self.input_binds = Some(input_binds);
67 self.did_an_error_occur()
68 }
69
70 fn last_error_message(&self) -> String {
71 unsafe { CStr::from_ptr(ffi::mysql_stmt_error(self.stmt.as_ptr())) }
72 .to_string_lossy()
73 .into_owned()
74 }
75
76 pub(super) fn metadata(&self) -> QueryResult<StatementMetadata> {
77 use crate::result::Error::DeserializationError;
78
79 let result_ptr = unsafe { ffi::mysql_stmt_result_metadata(self.stmt.as_ptr()) };
80 self.did_an_error_occur()?;
81 NonNull::new(result_ptr)
82 .map(StatementMetadata::new)
83 .ok_or_else(|| DeserializationError("No metadata exists".into()))
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 match last_error_number {
106 1062 | 1586 | 1859 => DatabaseErrorKind::UniqueViolation,
107 1216 | 1217 | 1451 | 1452 | 1830 | 1834 => DatabaseErrorKind::ForeignKeyViolation,
108 1792 => DatabaseErrorKind::ReadOnlyTransaction,
109 1048 | 1364 => DatabaseErrorKind::NotNullViolation,
110 3819 => DatabaseErrorKind::CheckViolation,
111 1213 => DatabaseErrorKind::SerializationFailure,
112 _ => DatabaseErrorKind::Unknown,
113 }
114 }
115
116 pub unsafe fn bind_result(&self, binds: *mut ffi::MYSQL_BIND) -> QueryResult<()> {
119 unsafe {
120 ffi::mysql_stmt_bind_result(self.stmt.as_ptr(), binds);
121 }
122 self.did_an_error_occur()
123 }
124}
125
126impl<'a> MaybeCached<'a, Statement> {
127 pub(super) fn execute_statement(
128 self,
129 binds: &mut OutputBinds,
130 ) -> QueryResult<StatementUse<'a>> {
131 unsafe {
132 binds.with_mysql_binds(|bind_ptr| self.bind_result(bind_ptr))?;
133 self.execute()
134 }
135 }
136
137 pub(super) unsafe fn execute(self) -> QueryResult<StatementUse<'a>> {
141 unsafe {
142 ffi::mysql_stmt_execute(self.stmt.as_ptr());
143 }
144 self.did_an_error_occur()?;
145 unsafe {
146 ffi::mysql_stmt_store_result(self.stmt.as_ptr());
147 }
148 let ret = StatementUse { inner: self };
149 ret.inner.did_an_error_occur()?;
150 Ok(ret)
151 }
152}
153
154impl Drop for Statement {
155 fn drop(&mut self) {
156 unsafe { ffi::mysql_stmt_close(self.stmt.as_ptr()) };
157 }
158}
159
160#[allow(missing_debug_implementations)]
161pub(super) struct StatementUse<'a> {
162 inner: MaybeCached<'a, Statement>,
163}
164
165impl StatementUse<'_> {
166 pub(in crate::mysql::connection) fn affected_rows(&self) -> QueryResult<usize> {
167 let affected_rows = unsafe { ffi::mysql_stmt_affected_rows(self.inner.stmt.as_ptr()) };
168 affected_rows
169 .try_into()
170 .map_err(|e| Error::DeserializationError(Box::new(e)))
171 }
172
173 pub(in crate::mysql::connection) unsafe fn result_size(&mut self) -> QueryResult<usize> {
176 let size = unsafe { ffi::mysql_stmt_num_rows(self.inner.stmt.as_ptr()) };
177 usize::try_from(size).map_err(|e| Error::DeserializationError(Box::new(e)))
178 }
179
180 pub(super) fn populate_row_buffers(&self, binds: &mut OutputBinds) -> QueryResult<Option<()>> {
181 let next_row_result = unsafe { ffi::mysql_stmt_fetch(self.inner.stmt.as_ptr()) };
182 if next_row_result < 0 {
183 self.inner.did_an_error_occur().map(Some)
184 } else {
185 #[allow(clippy::cast_sign_loss)] match next_row_result as libc::c_uint {
187 ffi::MYSQL_NO_DATA => Ok(None),
188 ffi::MYSQL_DATA_TRUNCATED => binds.populate_dynamic_buffers(self).map(Some),
189 0 => {
190 binds.update_buffer_lengths();
191 Ok(Some(()))
192 }
193 _error => self.inner.did_an_error_occur().map(Some),
194 }
195 }
196 }
197
198 pub(in crate::mysql::connection) unsafe fn fetch_column(
199 &self,
200 bind: &mut ffi::MYSQL_BIND,
201 idx: usize,
202 offset: usize,
203 ) -> QueryResult<()> {
204 unsafe {
205 ffi::mysql_stmt_fetch_column(
206 self.inner.stmt.as_ptr(),
207 bind,
208 idx.try_into()
209 .map_err(|e| Error::DeserializationError(Box::new(e)))?,
210 offset as libc::c_ulong,
211 );
212 }
213 self.inner.did_an_error_occur()
214 }
215
216 pub(in crate::mysql::connection) unsafe fn bind_result(
219 &self,
220 binds: *mut ffi::MYSQL_BIND,
221 ) -> QueryResult<()> {
222 unsafe { self.inner.bind_result(binds) }
223 }
224}
225
226impl Drop for StatementUse<'_> {
227 fn drop(&mut self) {
228 unsafe {
229 ffi::mysql_stmt_free_result(self.inner.stmt.as_ptr());
230 }
231 }
232}