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::CONNECTION_EXCEPTION)
77 | Some(error_codes::CONNECTION_FAILURE)
78 | Some(error_codes::SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION)
79 | Some(error_codes::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION) => {
80 DatabaseErrorKind::ClosedConnection
81 }
82 _ => DatabaseErrorKind::Unknown,
83 };
84 let error_information = Box::new(PgErrorInformation(internal_result));
85 let conn_status = conn.get_status();
86 if conn_status == ConnStatusType::CONNECTION_BAD {
87 error_kind = DatabaseErrorKind::ClosedConnection;
88 }
89 Err(Error::DatabaseError(error_kind, error_information))
90 }
91 }
92 }
93
94 pub(super) fn rows_affected(&self) -> usize {
95 unsafe {
96 let count_char_ptr = PQcmdTuples(self.internal_result.as_ptr());
97 let count_bytes = CStr::from_ptr(count_char_ptr).to_bytes();
98 let count_str = str::from_utf8_unchecked(count_bytes);
101 match count_str {
102 "" => 0,
103 _ => count_str
104 .parse()
105 .expect("Error parsing `rows_affected` as integer value"),
106 }
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 unsafe {
128 let value_ptr =
129 PQgetvalue(self.internal_result.as_ptr(), row_idx, col_idx) as *const u8;
130 let num_bytes = PQgetlength(self.internal_result.as_ptr(), row_idx, col_idx);
131 Some(slice::from_raw_parts(
132 value_ptr,
133 num_bytes
134 .try_into()
135 .expect("Diesel expects at least a 32 bit operating system"),
136 ))
137 }
138 }
139 }
140
141 pub(super) fn is_null(&self, row_idx: usize, col_idx: usize) -> bool {
142 let row_idx = row_idx
143 .try_into()
144 .expect("Row indices are expected to fit into 32 bit");
145 let col_idx = col_idx
146 .try_into()
147 .expect("Column indices are expected to fit into 32 bit");
148
149 unsafe { 0 != PQgetisnull(self.internal_result.as_ptr(), row_idx, col_idx) }
150 }
151
152 pub(in crate::pg) fn column_type(&self, col_idx: usize) -> NonZeroU32 {
153 let col_idx: i32 = col_idx
154 .try_into()
155 .expect("Column indices are expected to fit into 32 bit");
156 let type_oid = unsafe { PQftype(self.internal_result.as_ptr(), col_idx) };
157 NonZeroU32::new(type_oid).expect(
158 "Got a zero oid from postgres. If you see this error message \
159 please report it as issue on the diesel github bug tracker.",
160 )
161 }
162
163 #[inline(always)] pub(super) fn column_name(&self, col_idx: usize) -> Option<&str> {
165 self.column_name_map
166 .get_or_init(|| {
167 (0..self.column_count)
168 .map(|idx| unsafe {
169 let ptr = PQfname(self.internal_result.as_ptr(), idx as libc::c_int);
173 if ptr.is_null() {
174 None
175 } else {
176 Some(CStr::from_ptr(ptr).to_str().expect(
177 "Expect postgres field names to be UTF-8, because we \
178 requested UTF-8 encoding on connection setup",
179 ) as *const str)
180 }
181 })
182 .collect()
183 })
184 .get(col_idx)
185 .and_then(|n| {
186 n.map(|n: *const str| unsafe {
187 &*n
190 })
191 })
192 }
193
194 pub(super) fn column_count(&self) -> usize {
195 self.column_count.try_into().expect(
196 "Diesel expects to run on a >= 32 bit OS \
197 (or libpq is giving out negative column count)",
198 )
199 }
200}
201
202struct PgErrorInformation(RawResult);
203
204impl DatabaseErrorInformation for PgErrorInformation {
205 fn message(&self) -> &str {
206 get_result_field(self.0.as_ptr(), ResultField::MessagePrimary)
207 .unwrap_or_else(|| self.0.error_message())
208 }
209
210 fn details(&self) -> Option<&str> {
211 get_result_field(self.0.as_ptr(), ResultField::MessageDetail)
212 }
213
214 fn hint(&self) -> Option<&str> {
215 get_result_field(self.0.as_ptr(), ResultField::MessageHint)
216 }
217
218 fn table_name(&self) -> Option<&str> {
219 get_result_field(self.0.as_ptr(), ResultField::TableName)
220 }
221
222 fn column_name(&self) -> Option<&str> {
223 get_result_field(self.0.as_ptr(), ResultField::ColumnName)
224 }
225
226 fn constraint_name(&self) -> Option<&str> {
227 get_result_field(self.0.as_ptr(), ResultField::ConstraintName)
228 }
229
230 fn statement_position(&self) -> Option<i32> {
231 let str_pos = get_result_field(self.0.as_ptr(), ResultField::StatementPosition)?;
232 str_pos.parse::<i32>().ok()
233 }
234}
235
236#[repr(i32)]
241enum ResultField {
242 SqlState = 'C' as i32,
243 MessagePrimary = 'M' as i32,
244 MessageDetail = 'D' as i32,
245 MessageHint = 'H' as i32,
246 TableName = 't' as i32,
247 ColumnName = 'c' as i32,
248 ConstraintName = 'n' as i32,
249 StatementPosition = 'P' as i32,
250}
251
252fn get_result_field<'a>(res: *mut PGresult, field: ResultField) -> Option<&'a str> {
253 let ptr = unsafe { PQresultErrorField(res, field as libc::c_int) };
254 if ptr.is_null() {
255 return None;
256 }
257
258 let c_str = unsafe { CStr::from_ptr(ptr) };
259 c_str.to_str().ok()
260}
261
262mod error_codes {
263 pub(in crate::pg::connection) const CONNECTION_EXCEPTION: &str = "08000";
268 pub(in crate::pg::connection) const CONNECTION_FAILURE: &str = "08006";
269 pub(in crate::pg::connection) const SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION: &str = "08001";
270 pub(in crate::pg::connection) const SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION: &str =
271 "08004";
272 pub(in crate::pg::connection) const NOT_NULL_VIOLATION: &str = "23502";
273 pub(in crate::pg::connection) const FOREIGN_KEY_VIOLATION: &str = "23503";
274 pub(in crate::pg::connection) const UNIQUE_VIOLATION: &str = "23505";
275 pub(in crate::pg::connection) const CHECK_VIOLATION: &str = "23514";
276 pub(in crate::pg::connection) const READ_ONLY_TRANSACTION: &str = "25006";
277 pub(in crate::pg::connection) const SERIALIZATION_FAILURE: &str = "40001";
278}