diesel/pg/connection/
result.rs
1#![allow(unsafe_code)] extern 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 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 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 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)] 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 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 &*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#[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 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}