Skip to content

Commit 7e378c4

Browse files
committed
Construct error messages properly when libpq returns NULL result
I had originally thought that this was some weird condition that was the result of the server getting a query with more than `34464` (`i16::MAX`) bind parameters, where we got back an error but it had no message. We panic in that case, as PG is documented that all errors have a message. It turns out I was wrong for two reasons. First, the number is 65535 (`u16::MAX`), not `34464`. (This might have been something changed on the PG side in 9.6 which was released since I last looked at this). Second, we were not getting back an error with no message, we were getting a `NULL` return value from libpq. From [their docs][]: [their docs]: https://www.postgresql.org/docs/9.6/static/libpq-exec.html#LIBPQ-PQPREPARE > A null result indicates out-of-memory or inability to send the command > at all. Use PQerrorMessage to get more information about such errors. `PQerrorMessage` means the error is on the connection. The reason that we had no result was that the PG wire protocol uses a `u16` for the number of bind parameters, so there's no way for the driver to have even sent the message to the server. Once we realized the error condition that we were seeing is more general than just the bind parameter case, I've refactored the code to perform a not null check as early as possible, and hide the `*mut PGresult`. The test itself is written in a somewhat strange fashion. We're constructing a query that has 70_000 bind parameters, as this is the only case that I know of which will always cause libpq to return null. I don't care what the error message is, just that it's not an empty string. I've also written the test to explicitly match instead of unwrap and use `#[should_panic]` because I want to be sure that it *didn't* panic. Ideally we would never see `*mut PGresult` anywhere in the code, and use `Unique<PGresult>` instead. However, it is currently unstable. I had originally implemented this to use `Unique` if `feature = "unstable"` was set, but deleted it as the `#[cfg]` branches made the code harder to comprehend. The only real benefit `Unique` gives us here is clarity of intent. For the not-null and non-aliased hints to result in any optimization by the compiler would require libpq to be statically linked, and LTO to be happening at the LLVM layer, neither of which are commonly true. We know that `Send` and `Sync` are safe to implement on our new struct, because the pointer is unaliased. We can delete those impls if we switch to using `Unique` in the future, as it already implements those traits. Fixes diesel-rs#446.
1 parent b757cec commit 7e378c4

8 files changed

Lines changed: 96 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
1717
* `debug_sql!` can now properly be used with types from `chrono` or
1818
`std::time`.
1919

20+
* When using PostgreSQL, attempting to get the error message of a query which
21+
could not be transmitted to the server (such as a query with greater than
22+
65535 bind parameters) will no longer panic.
23+
2024
## [0.9.0] - 2016-12-08
2125

2226
### Added

diesel/src/pg/connection/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl SimpleConnection for PgConnection {
4141
let inner_result = unsafe {
4242
self.raw_connection.exec(query.as_ptr())
4343
};
44-
try!(PgResult::new(inner_result));
44+
try!(PgResult::new(inner_result?));
4545
Ok(())
4646
}
4747
}

diesel/src/pg/connection/raw.rs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ impl RawConnection {
5757
}
5858
}
5959

60-
pub unsafe fn exec(&self, query: *const libc::c_char) -> *mut PGresult {
61-
PQexec(self.internal_connection, query)
60+
pub unsafe fn exec(&self, query: *const libc::c_char) -> QueryResult<RawResult> {
61+
RawResult::new(PQexec(self.internal_connection, query), self)
6262
}
6363

6464
pub unsafe fn exec_params(
@@ -70,8 +70,8 @@ impl RawConnection {
7070
param_lengths: *const libc::c_int,
7171
param_formats: *const libc::c_int,
7272
result_format: libc::c_int,
73-
) -> *mut PGresult {
74-
PQexecParams(
73+
) -> QueryResult<RawResult> {
74+
let ptr = PQexecParams(
7575
self.internal_connection,
7676
query,
7777
param_count,
@@ -80,7 +80,8 @@ impl RawConnection {
8080
param_lengths,
8181
param_formats,
8282
result_format,
83-
)
83+
);
84+
RawResult::new(ptr, self)
8485
}
8586

8687
pub unsafe fn exec_prepared(
@@ -91,16 +92,17 @@ impl RawConnection {
9192
param_lengths: *const libc::c_int,
9293
param_formats: *const libc::c_int,
9394
result_format: libc::c_int,
94-
) -> *mut PGresult {
95-
PQexecPrepared(
95+
) -> QueryResult<RawResult> {
96+
let ptr = PQexecPrepared(
9697
self.internal_connection,
9798
stmt_name,
9899
param_count,
99100
param_values,
100101
param_lengths,
101102
param_formats,
102103
result_format,
103-
)
104+
);
105+
RawResult::new(ptr, self)
104106
}
105107

106108
pub unsafe fn prepare(
@@ -109,14 +111,15 @@ impl RawConnection {
109111
query: *const libc::c_char,
110112
param_count: libc::c_int,
111113
param_types: *const Oid,
112-
) -> *mut PGresult {
113-
PQprepare(
114+
) -> QueryResult<RawResult> {
115+
let ptr = PQprepare(
114116
self.internal_connection,
115117
stmt_name,
116118
query,
117119
param_count,
118120
param_types,
119-
)
121+
);
122+
RawResult::new(ptr, self)
120123
}
121124
}
122125

@@ -167,3 +170,37 @@ impl Drop for PgString {
167170
}
168171
}
169172
}
173+
174+
/// Internal wrapper around a `*mut PGresult` which is known to be not-null, and
175+
/// have no aliases. This wrapper is to ensure that it's always properly
176+
/// dropped.
177+
///
178+
/// If `Unique` is ever stabilized, we should use it here.
179+
#[allow(missing_debug_implementations)]
180+
pub struct RawResult(*mut PGresult);
181+
182+
unsafe impl Send for RawResult {}
183+
unsafe impl Sync for RawResult {}
184+
185+
impl RawResult {
186+
fn new(ptr: *mut PGresult, conn: &RawConnection) -> QueryResult<Self> {
187+
if ptr.is_null() {
188+
Err(Error::DatabaseError(
189+
DatabaseErrorKind::UnableToSendCommand,
190+
Box::new(conn.last_error_message()),
191+
))
192+
} else {
193+
Ok(RawResult(ptr))
194+
}
195+
}
196+
197+
pub fn as_ptr(&self) -> *mut PGresult {
198+
self.0
199+
}
200+
}
201+
202+
impl Drop for RawResult {
203+
fn drop(&mut self) {
204+
unsafe { PQclear(self.0) }
205+
}
206+
}

diesel/src/pg/connection/result.rs

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,39 @@ extern crate libc;
33

44
use result::{Error, QueryResult, DatabaseErrorInformation, DatabaseErrorKind};
55
use super::row::PgRow;
6+
use super::raw::RawResult;
67

78
use self::pq_sys::*;
89
use std::ffi::CStr;
910
use std::{str, slice};
1011

1112
pub struct PgResult {
12-
internal_result: *mut PGresult,
13+
internal_result: RawResult,
1314
}
1415

1516
impl PgResult {
16-
pub fn new(internal_result: *mut PGresult) -> QueryResult<Self> {
17-
let result_status = unsafe { PQresultStatus(internal_result) };
17+
pub fn new(internal_result: RawResult) -> QueryResult<Self> {
18+
let result_status = unsafe { PQresultStatus(internal_result.as_ptr()) };
1819
match result_status {
1920
PGRES_COMMAND_OK | PGRES_TUPLES_OK => {
2021
Ok(PgResult {
2122
internal_result: internal_result,
2223
})
2324
},
2425
_ => {
25-
let error_information = Box::new(PgErrorInformation(internal_result));
26-
let error_kind = match get_result_field(internal_result, ResultField::SqlState) {
26+
let error_kind = match get_result_field(internal_result.as_ptr(), ResultField::SqlState) {
2727
Some(error_codes::UNIQUE_VIOLATION) => DatabaseErrorKind::UniqueViolation,
2828
_ => DatabaseErrorKind::__Unknown,
2929
};
30+
let error_information = Box::new(PgErrorInformation(internal_result));
3031
Err(Error::DatabaseError(error_kind, error_information))
3132
}
3233
}
3334
}
3435

3536
pub fn rows_affected(&self) -> usize {
3637
unsafe {
37-
let count_char_ptr = PQcmdTuples(self.internal_result);
38+
let count_char_ptr = PQcmdTuples(self.internal_result.as_ptr());
3839
let count_bytes = CStr::from_ptr(count_char_ptr).to_bytes();
3940
let count_str = str::from_utf8_unchecked(count_bytes);
4041
match count_str {
@@ -45,7 +46,7 @@ impl PgResult {
4546
}
4647

4748
pub fn num_rows(&self) -> usize {
48-
unsafe { PQntuples(self.internal_result) as usize }
49+
unsafe { PQntuples(self.internal_result.as_ptr()) as usize }
4950
}
5051

5152
pub fn get_row(&self, idx: usize) -> PgRow {
@@ -59,8 +60,9 @@ impl PgResult {
5960
let row_idx = row_idx as libc::c_int;
6061
let col_idx = col_idx as libc::c_int;
6162
unsafe {
62-
let value_ptr = PQgetvalue(self.internal_result, row_idx, col_idx) as *const u8;
63-
let num_bytes = PQgetlength(self.internal_result, row_idx, col_idx);
63+
let value_ptr = PQgetvalue(self.internal_result.as_ptr(), row_idx, col_idx)
64+
as *const u8;
65+
let num_bytes = PQgetlength(self.internal_result.as_ptr(), row_idx, col_idx);
6466
Some(slice::from_raw_parts(value_ptr, num_bytes as usize))
6567
}
6668
}
@@ -69,56 +71,42 @@ impl PgResult {
6971
pub fn is_null(&self, row_idx: usize, col_idx: usize) -> bool {
7072
unsafe {
7173
0 != PQgetisnull(
72-
self.internal_result,
74+
self.internal_result.as_ptr(),
7375
row_idx as libc::c_int,
7476
col_idx as libc::c_int,
7577
)
7678
}
7779
}
7880
}
7981

80-
impl Drop for PgResult {
81-
fn drop(&mut self) {
82-
unsafe { PQclear(self.internal_result) };
83-
}
84-
}
85-
86-
struct PgErrorInformation(*mut PGresult);
87-
88-
unsafe impl Send for PgErrorInformation {}
89-
90-
impl Drop for PgErrorInformation {
91-
fn drop(&mut self) {
92-
unsafe { PQclear(self.0) };
93-
}
94-
}
82+
struct PgErrorInformation(RawResult);
9583

9684
impl DatabaseErrorInformation for PgErrorInformation {
9785
fn message(&self) -> &str {
98-
match get_result_field(self.0, ResultField::MessagePrimary) {
86+
match get_result_field(self.0.as_ptr(), ResultField::MessagePrimary) {
9987
Some(e) => e,
10088
None => unreachable!("Per PGs documentation, all errors should have a message"),
10189
}
10290
}
10391

10492
fn details(&self) -> Option<&str> {
105-
get_result_field(self.0, ResultField::MessageDetail)
93+
get_result_field(self.0.as_ptr(), ResultField::MessageDetail)
10694
}
10795

10896
fn hint(&self) -> Option<&str> {
109-
get_result_field(self.0, ResultField::MessageHint)
97+
get_result_field(self.0.as_ptr(), ResultField::MessageHint)
11098
}
11199

112100
fn table_name(&self) -> Option<&str> {
113-
get_result_field(self.0, ResultField::TableName)
101+
get_result_field(self.0.as_ptr(), ResultField::TableName)
114102
}
115103

116104
fn column_name(&self) -> Option<&str> {
117-
get_result_field(self.0, ResultField::ColumnName)
105+
get_result_field(self.0.as_ptr(), ResultField::ColumnName)
118106
}
119107

120108
fn constraint_name(&self) -> Option<&str> {
121-
get_result_field(self.0, ResultField::ConstraintName)
109+
get_result_field(self.0.as_ptr(), ResultField::ConstraintName)
122110
}
123111
}
124112

diesel/src/pg/connection/stmt/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl Query {
6363
}
6464
};
6565

66-
PgResult::new(internal_res)
66+
PgResult::new(internal_res?)
6767
}
6868

6969
pub fn sql(sql: &str, param_types: Option<Vec<u32>>) -> QueryResult<Self> {
@@ -90,7 +90,7 @@ impl Query {
9090
param_types_to_ptr(Some(&param_types)),
9191
)
9292
};
93-
try!(PgResult::new(internal_result));
93+
try!(PgResult::new(internal_result?));
9494

9595
Ok(Query::Prepared {
9696
name: name,

diesel/src/result.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub enum Error {
2626
/// bump.
2727
pub enum DatabaseErrorKind {
2828
UniqueViolation,
29+
UnableToSendCommand,
2930
#[doc(hidden)]
3031
__Unknown, // Match against _ instead, more variants may be added in the future
3132
}

diesel_tests/tests/internal_details.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,23 @@ fn bind_params_are_passed_for_null_when_not_inserting() {
1111
.filter(AsExpression::<Nullable<Integer>>::as_expression(None::<i32>).is_null());
1212
assert_eq!(Ok(1), query.first(&connection));
1313
}
14+
15+
#[test]
16+
#[cfg(feature = "postgres")]
17+
fn query_which_cannot_be_transmitted_gives_proper_error_message() {
18+
use schema::comments::dsl::*;
19+
use diesel::result::Error::DatabaseError;
20+
use diesel::result::DatabaseErrorKind::UnableToSendCommand;
21+
22+
// Create a query with 90000 binds, 2 binds per row
23+
let data: &[NewComment<'static>] = &[NewComment(1, "hi"); 45_000];
24+
let query_with_to_many_binds = insert(data).into(comments);
25+
26+
match query_with_to_many_binds.execute(&connection()) {
27+
Ok(_) => panic!("We successfully executed a query with 90k binds. \
28+
We need to find a new query to test which can't be represented by \
29+
the wire protocol."),
30+
Err(DatabaseError(UnableToSendCommand, info)) => assert_ne!("", info.message()),
31+
Err(_) => panic!("We got back the wrong kind of error. This test is invalid."),
32+
}
33+
}

diesel_tests/tests/schema.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl NewPost {
126126
}
127127
}
128128

129-
#[derive(Insertable)]
129+
#[derive(Debug, Clone, Copy, Insertable)]
130130
#[table_name="comments"]
131131
pub struct NewComment<'a>(
132132
#[column_name(post_id)]

0 commit comments

Comments
 (0)