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