diesel/pg/connection/
result.rs

1#![allow(unsafe_code)] // ffi code
2extern crate pq_sys;
3
4use self::pq_sys::*;
5use std::ffi::CStr;
6use std::num::NonZeroU32;
7use std::os::raw as libc;
8use std::rc::Rc;
9use std::{slice, str};
10
11use super::raw::{RawConnection, RawResult};
12use super::row::PgRow;
13use crate::result::{DatabaseErrorInformation, DatabaseErrorKind, Error, QueryResult};
14use std::cell::OnceCell;
15
16#[allow(missing_debug_implementations)]
17pub struct PgResult {
18    internal_result: RawResult,
19    column_count: libc::c_int,
20    row_count: libc::c_int,
21    // We store field names as pointer
22    // as we cannot put a correct lifetime here
23    // The value is valid as long as we haven't freed `RawResult`
24    column_name_map: OnceCell<Vec<Option<*const str>>>,
25}
26
27impl PgResult {
28    #[allow(clippy::new_ret_no_self)]
29    pub(super) fn new(internal_result: RawResult, conn: &RawConnection) -> QueryResult<Self> {
30        let result_status = unsafe { PQresultStatus(internal_result.as_ptr()) };
31        match result_status {
32            ExecStatusType::PGRES_SINGLE_TUPLE
33            | ExecStatusType::PGRES_COMMAND_OK
34            | ExecStatusType::PGRES_COPY_IN
35            | ExecStatusType::PGRES_COPY_OUT
36            | ExecStatusType::PGRES_TUPLES_OK => {
37                let column_count = unsafe { PQnfields(internal_result.as_ptr()) };
38                let row_count = unsafe { PQntuples(internal_result.as_ptr()) };
39                Ok(PgResult {
40                    internal_result,
41                    column_count,
42                    row_count,
43                    column_name_map: OnceCell::new(),
44                })
45            }
46            ExecStatusType::PGRES_EMPTY_QUERY => {
47                let error_message = "Received an empty query".to_string();
48                Err(Error::DatabaseError(
49                    DatabaseErrorKind::Unknown,
50                    Box::new(error_message),
51                ))
52            }
53            _ => {
54                // "clearing" the connection by polling result till we get a null.
55                // this will indicate that the previous command is complete and
56                // the same connection is ready to process next command.
57                // https://www.postgresql.org/docs/current/libpq-async.html
58                while conn.get_next_result().map_or(true, |r| r.is_some()) {}
59
60                let mut error_kind =
61                    match get_result_field(internal_result.as_ptr(), ResultField::SqlState) {
62                        Some(error_codes::UNIQUE_VIOLATION) => DatabaseErrorKind::UniqueViolation,
63                        Some(error_codes::FOREIGN_KEY_VIOLATION) => {
64                            DatabaseErrorKind::ForeignKeyViolation
65                        }
66                        Some(error_codes::SERIALIZATION_FAILURE) => {
67                            DatabaseErrorKind::SerializationFailure
68                        }
69                        Some(error_codes::READ_ONLY_TRANSACTION) => {
70                            DatabaseErrorKind::ReadOnlyTransaction
71                        }
72                        Some(error_codes::NOT_NULL_VIOLATION) => {
73                            DatabaseErrorKind::NotNullViolation
74                        }
75                        Some(error_codes::CHECK_VIOLATION) => DatabaseErrorKind::CheckViolation,
76                        Some(error_codes::RESTRICT_VIOLATION) => {
77                            DatabaseErrorKind::RestrictViolation
78                        }
79                        Some(error_codes::EXCLUSION_VIOLATION) => {
80                            DatabaseErrorKind::ExclusionViolation
81                        }
82                        Some(error_codes::CONNECTION_EXCEPTION)
83                        | Some(error_codes::CONNECTION_FAILURE)
84                        | Some(error_codes::SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION)
85                        | Some(error_codes::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION) => {
86                            DatabaseErrorKind::ClosedConnection
87                        }
88                        _ => DatabaseErrorKind::Unknown,
89                    };
90                let error_information = Box::new(PgErrorInformation(internal_result));
91                let conn_status = conn.get_status();
92                if conn_status == ConnStatusType::CONNECTION_BAD {
93                    error_kind = DatabaseErrorKind::ClosedConnection;
94                }
95                Err(Error::DatabaseError(error_kind, error_information))
96            }
97        }
98    }
99
100    pub(super) fn rows_affected(&self) -> usize {
101        unsafe {
102            let count_char_ptr = PQcmdTuples(self.internal_result.as_ptr());
103            let count_bytes = CStr::from_ptr(count_char_ptr).to_bytes();
104            // Using from_utf8_unchecked is ok here because, we've set the
105            // client encoding to utf8
106            let count_str = str::from_utf8_unchecked(count_bytes);
107            match count_str {
108                "" => 0,
109                _ => count_str
110                    .parse()
111                    .expect("Error parsing `rows_affected` as integer value"),
112            }
113        }
114    }
115
116    pub(super) fn num_rows(&self) -> usize {
117        self.row_count.try_into().expect(
118            "Diesel expects to run on a >= 32 bit OS \
119                (or libpq is giving out negative row count)",
120        )
121    }
122
123    pub(super) fn get_row(self: Rc<Self>, idx: usize) -> PgRow {
124        PgRow::new(self, idx)
125    }
126
127    pub(super) fn get(&self, row_idx: usize, col_idx: usize) -> Option<&[u8]> {
128        if self.is_null(row_idx, col_idx) {
129            None
130        } else {
131            let row_idx = row_idx.try_into().ok()?;
132            let col_idx = col_idx.try_into().ok()?;
133            unsafe {
134                let value_ptr =
135                    PQgetvalue(self.internal_result.as_ptr(), row_idx, col_idx) as *const u8;
136                let num_bytes = PQgetlength(self.internal_result.as_ptr(), row_idx, col_idx);
137                Some(slice::from_raw_parts(
138                    value_ptr,
139                    num_bytes
140                        .try_into()
141                        .expect("Diesel expects at least a 32 bit operating system"),
142                ))
143            }
144        }
145    }
146
147    pub(super) fn is_null(&self, row_idx: usize, col_idx: usize) -> bool {
148        let row_idx = row_idx
149            .try_into()
150            .expect("Row indices are expected to fit into 32 bit");
151        let col_idx = col_idx
152            .try_into()
153            .expect("Column indices are expected to fit into 32 bit");
154
155        unsafe { 0 != PQgetisnull(self.internal_result.as_ptr(), row_idx, col_idx) }
156    }
157
158    pub(in crate::pg) fn column_type(&self, col_idx: usize) -> NonZeroU32 {
159        let col_idx: i32 = col_idx
160            .try_into()
161            .expect("Column indices are expected to fit into 32 bit");
162        let type_oid = unsafe { PQftype(self.internal_result.as_ptr(), col_idx) };
163        NonZeroU32::new(type_oid).expect(
164            "Got a zero oid from postgres. If you see this error message \
165             please report it as issue on the diesel github bug tracker.",
166        )
167    }
168
169    #[inline(always)] // benchmarks indicate a ~1.7% improvement in instruction count for this
170    pub(super) fn column_name(&self, col_idx: usize) -> Option<&str> {
171        self.column_name_map
172            .get_or_init(|| {
173                (0..self.column_count)
174                    .map(|idx| unsafe {
175                        // https://www.postgresql.org/docs/13/libpq-exec.html#LIBPQ-PQFNAME
176                        // states that the returned ptr is valid till the underlying result is freed
177                        // That means we can couple the lifetime to self
178                        let ptr = PQfname(self.internal_result.as_ptr(), idx as libc::c_int);
179                        if ptr.is_null() {
180                            None
181                        } else {
182                            Some(CStr::from_ptr(ptr).to_str().expect(
183                                "Expect postgres field names to be UTF-8, because we \
184                     requested UTF-8 encoding on connection setup",
185                            ) as *const str)
186                        }
187                    })
188                    .collect()
189            })
190            .get(col_idx)
191            .and_then(|n| {
192                n.map(|n: *const str| unsafe {
193                    // The pointer is valid for the same lifetime as &self
194                    // so we can dereference it without any check
195                    &*n
196                })
197            })
198    }
199
200    pub(super) fn column_count(&self) -> usize {
201        self.column_count.try_into().expect(
202            "Diesel expects to run on a >= 32 bit OS \
203                (or libpq is giving out negative column count)",
204        )
205    }
206}
207
208struct PgErrorInformation(RawResult);
209
210impl DatabaseErrorInformation for PgErrorInformation {
211    fn message(&self) -> &str {
212        get_result_field(self.0.as_ptr(), ResultField::MessagePrimary)
213            .unwrap_or_else(|| self.0.error_message())
214    }
215
216    fn details(&self) -> Option<&str> {
217        get_result_field(self.0.as_ptr(), ResultField::MessageDetail)
218    }
219
220    fn hint(&self) -> Option<&str> {
221        get_result_field(self.0.as_ptr(), ResultField::MessageHint)
222    }
223
224    fn table_name(&self) -> Option<&str> {
225        get_result_field(self.0.as_ptr(), ResultField::TableName)
226    }
227
228    fn column_name(&self) -> Option<&str> {
229        get_result_field(self.0.as_ptr(), ResultField::ColumnName)
230    }
231
232    fn constraint_name(&self) -> Option<&str> {
233        get_result_field(self.0.as_ptr(), ResultField::ConstraintName)
234    }
235
236    fn statement_position(&self) -> Option<i32> {
237        let str_pos = get_result_field(self.0.as_ptr(), ResultField::StatementPosition)?;
238        str_pos.parse::<i32>().ok()
239    }
240}
241
242/// Represents valid options to
243/// [`PQresultErrorField`](https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQRESULTERRORFIELD)
244/// Their values are defined as C preprocessor macros, and therefore are not exported by libpq-sys.
245/// Their values can be found in `postgres_ext.h`
246#[repr(i32)]
247enum ResultField {
248    SqlState = 'C' as i32,
249    MessagePrimary = 'M' as i32,
250    MessageDetail = 'D' as i32,
251    MessageHint = 'H' as i32,
252    TableName = 't' as i32,
253    ColumnName = 'c' as i32,
254    ConstraintName = 'n' as i32,
255    StatementPosition = 'P' as i32,
256}
257
258fn get_result_field<'a>(res: *mut PGresult, field: ResultField) -> Option<&'a str> {
259    let ptr = unsafe { PQresultErrorField(res, field as libc::c_int) };
260    if ptr.is_null() {
261        return None;
262    }
263
264    let c_str = unsafe { CStr::from_ptr(ptr) };
265    c_str.to_str().ok()
266}
267
268mod error_codes {
269    //! These error codes are documented at
270    //! <https://www.postgresql.org/docs/current/errcodes-appendix.html>
271    //!
272    //! They are not exposed programmatically through libpq.
273    pub(in crate::pg::connection) const CONNECTION_EXCEPTION: &str = "08000";
274    pub(in crate::pg::connection) const CONNECTION_FAILURE: &str = "08006";
275    pub(in crate::pg::connection) const SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION: &str = "08001";
276    pub(in crate::pg::connection) const SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION: &str =
277        "08004";
278    pub(in crate::pg::connection) const RESTRICT_VIOLATION: &str = "23001";
279    pub(in crate::pg::connection) const NOT_NULL_VIOLATION: &str = "23502";
280    pub(in crate::pg::connection) const FOREIGN_KEY_VIOLATION: &str = "23503";
281    pub(in crate::pg::connection) const UNIQUE_VIOLATION: &str = "23505";
282    pub(in crate::pg::connection) const CHECK_VIOLATION: &str = "23514";
283    pub(in crate::pg::connection) const EXCLUSION_VIOLATION: &str = "23P01";
284    pub(in crate::pg::connection) const READ_ONLY_TRANSACTION: &str = "25006";
285    pub(in crate::pg::connection) const SERIALIZATION_FAILURE: &str = "40001";
286}