Skip to main content

diesel/pg/connection/
result.rs

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