Skip to content

Commit 03295f8

Browse files
authored
Merge pull request #4540 from achanda/prepare-2.2.9
Prepare a 2.2.9 release
2 parents 2464571 + 652a816 commit 03295f8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+451
-269
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ jobs:
126126
echo "MYSQL_UNIT_TEST_DATABASE_URL=mysql://root:[email protected]/diesel_unit_test" >> $GITHUB_ENV
127127
128128
- name: Install postgres (MacOS)
129-
if: matrix.os == 'macos-13' || matrix.os == 'macos-15' && matrix.backend == 'postgres'
129+
if: (matrix.os == 'macos-13' || matrix.os == 'macos-15') && matrix.backend == 'postgres'
130130
run: |
131+
brew update
131132
brew install postgresql@14
132133
brew services start postgresql@14
133134
sleep 3
@@ -519,7 +520,7 @@ jobs:
519520
- name: Install Mysql (Linux)
520521
run: |
521522
sudo apt-get update
522-
sudo apt-get -y install libmysqlclient-dev llvm
523+
sudo apt-get -y install libmysqlclient-dev llvm libtirpc-dev
523524
sudo systemctl start mysql.service
524525
sleep 5
525526
mysql -e "create database diesel_test; create database diesel_unit_test; grant all on \`diesel_%\`.* to 'root'@'localhost';" -uroot -proot

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,15 @@ Increasing the minimal supported Rust version will always be coupled at least wi
1010

1111
## Unreleased
1212

13-
## [2.2.8] 2025-03-03
13+
## [2.2.9] 2025-04-04
14+
### Fixed
1415

16+
* Fix an issue where `diesel migration generate --diff-schema` incorrectly uses the primary key of table B as the referenced column rather than the primary key of table A when B has a foreign key pointing to table A.
17+
* Bump maximal supported libsqlite3-sys version to 0.32.0 and add explicit feature entries for the `uuid` and `serde_json` feature.
18+
* Fixed an issue where diesel generated unnamed prepared statements would fail with an `unanmed prepared statement not found` error with pgbouncer.
19+
* Fix an issue with converting `ipnet::Ipnet` values with an subnet to SQL values
20+
21+
## [2.2.8] 2025-03-03
1522
### Fixed
1623

1724
* Allow `#[diesel(check_for_backend(_))]` to check fields with `#[diesel(embed)]` annotations
@@ -2190,3 +2197,4 @@ queries or set `PIPES_AS_CONCAT` manually.
21902197
[2.2.6]: https://github.com/diesel-rs/diesel/compare/v2.2.5...v2.2.6
21912198
[2.2.7]: https://github.com/diesel-rs/diesel/compare/v2.2.6...v2.2.7
21922199
[2.2.8]: https://github.com/diesel-rs/diesel/compare/v2.2.7...v2.2.8
2200+
[2.2.9]: https://github.com/diesel-rs/diesel/compare/v2.2.8...v2.2.9

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ rust-version = "1.78.0"
3535
include = ["src/**/*.rs", "tests/**/*.rs", "LICENSE-*", "README.md"]
3636

3737
[workspace.dependencies]
38-
libsqlite3-sys = ">=0.30.1,<0.32.0"
38+
libsqlite3-sys = ">=0.30.1,<0.33.0"
3939

4040
# Config for 'cargo dist'
4141
[workspace.metadata.dist]

diesel/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "diesel"
3-
version = "2.2.8"
3+
version = "2.2.9"
44
license = "MIT OR Apache-2.0"
55
description = "A safe, extensible ORM and Query Builder for PostgreSQL, SQLite, and MySQL"
66
readme = "README.md"
@@ -24,7 +24,7 @@ include = [
2424
byteorder = { version = "1.0", optional = true }
2525
chrono = { version = "0.4.20", optional = true, default-features = false, features = ["clock", "std"] }
2626
libc = { version = "0.2.0", optional = true }
27-
libsqlite3-sys = { version = ">=0.17.2, <0.32.0", optional = true, features = ["bundled_bindings"] }
27+
libsqlite3-sys = { version = ">=0.17.2, <0.33.0", optional = true, features = ["bundled_bindings"] }
2828
mysqlclient-sys = { version = ">=0.2.5, <0.5.0", optional = true }
2929
mysqlclient-src = { version = "0.1.0", optional = true }
3030
pq-sys = { version = ">=0.4.0, <0.8.0", optional = true }
@@ -80,6 +80,8 @@ i-implement-a-third-party-backend-and-opt-into-breaking-changes = []
8080
r2d2 = ["diesel_derives/r2d2", "dep:r2d2"]
8181
chrono = ["diesel_derives/chrono", "dep:chrono"]
8282
time = ["diesel_derives/time", "dep:time"]
83+
uuid = ["dep:uuid"]
84+
serde_json = ["dep:serde_json"]
8385
__with_asan_tests = [
8486
"libsqlite3-sys?/bundled",
8587
"libsqlite3-sys?/with-asan",

diesel/src/connection/statement_cache.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
//! - queries containing `IN` with bind parameters
3131
//! - This requires 1 bind parameter per value, and is therefore unbounded
3232
//! - `IN` with subselects are cached (assuming the subselect is safe to
33-
//! cache)
33+
//! cache)
3434
//! - `IN` statements for postgresql are cached as they use `= ANY($1)` instead
35-
//! which does not cause a unbound number of binds
35+
//! which does not cause an unbound number of binds
3636
//! - `INSERT` statements with a variable number of rows
3737
//! - The SQL varies based on the number of rows being inserted.
3838
//! - `UPDATE` statements

diesel/src/lib.rs

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -97,41 +97,41 @@
9797
//! The following error messages are common:
9898
//!
9999
//! * `the trait bound (diesel::sql_types::Integer, …, diesel::sql_types::Text): load_dsl::private::CompatibleType<YourModel, Pg> is not satisfied`
100-
//! while trying to execute a query:
101-
//! This error indicates a mismatch between what your query returns and what your model struct
102-
//! expects the query to return. The fields need to match in terms of field order, field type
103-
//! and field count. If you are sure that everything matches, double check the enabled diesel
104-
//! features (for support for types from other crates) and double check (via `cargo tree`)
105-
//! that there is only one version of such a shared crate in your dependency tree.
106-
//! Consider using [`#[derive(Selectable)]`](derive@crate::prelude::Selectable) +
107-
//! `#[diesel(check_for_backend(diesel::pg::Pg))]`
108-
//! to improve the generated error message.
100+
//! while trying to execute a query:
101+
//! This error indicates a mismatch between what your query returns and what your model struct
102+
//! expects the query to return. The fields need to match in terms of field order, field type
103+
//! and field count. If you are sure that everything matches, double check the enabled diesel
104+
//! features (for support for types from other crates) and double check (via `cargo tree`)
105+
//! that there is only one version of such a shared crate in your dependency tree.
106+
//! Consider using [`#[derive(Selectable)]`](derive@crate::prelude::Selectable) +
107+
//! `#[diesel(check_for_backend(diesel::pg::Pg))]`
108+
//! to improve the generated error message.
109109
//! * `the trait bound i32: diesel::Expression is not satisfied` in the context of `Insertable`
110-
//! model structs:
111-
//! This error indicates a type mismatch between the field you are trying to insert into the database
112-
//! and the actual database type. These error messages contain a line
113-
//! like ` = note: required for i32 to implement AsExpression<diesel::sql_types::Text>`
114-
//! that show both the provided rust side type (`i32` in that case) and the expected
115-
//! database side type (`Text` in that case).
110+
//! model structs:
111+
//! This error indicates a type mismatch between the field you are trying to insert into the database
112+
//! and the actual database type. These error messages contain a line
113+
//! like ` = note: required for i32 to implement AsExpression<diesel::sql_types::Text>`
114+
//! that show both the provided rust side type (`i32` in that case) and the expected
115+
//! database side type (`Text` in that case).
116116
//! * `the trait bound i32: AppearsOnTable<users::table> is not satisfied` in the context of `AsChangeset`
117-
//! model structs:
118-
//! This error indicates a type mismatch between the field you are trying to update and the actual
119-
//! database type. Double check your type mapping.
117+
//! model structs:
118+
//! This error indicates a type mismatch between the field you are trying to update and the actual
119+
//! database type. Double check your type mapping.
120120
//! * `the trait bound SomeLargeType: QueryFragment<Sqlite, SomeMarkerType> is not satisfied` while
121-
//! trying to execute a query.
122-
//! This error message indicates that a given query is not supported by your backend. This usually
123-
//! means that you are trying to use SQL features from one SQL dialect on a different database
124-
//! system. Double check your query that everything required is supported by the selected
125-
//! backend. If that's the case double check that the relevant feature flags are enabled
126-
//! (for example, `returning_clauses_for_sqlite_3_35` for enabling support for returning clauses in newer
127-
//! sqlite versions)
121+
//! trying to execute a query.
122+
//! This error message indicates that a given query is not supported by your backend. This usually
123+
//! means that you are trying to use SQL features from one SQL dialect on a different database
124+
//! system. Double check your query that everything required is supported by the selected
125+
//! backend. If that's the case double check that the relevant feature flags are enabled
126+
//! (for example, `returning_clauses_for_sqlite_3_35` for enabling support for returning clauses in newer
127+
//! sqlite versions)
128128
//! * `the trait bound posts::title: SelectableExpression<users::table> is not satisfied` while
129-
//! executing a query:
130-
//! This error message indicates that you're trying to select a field from a table
131-
//! that does not appear in your from clause. If your query joins the relevant table via
132-
//! [`left_join`](crate::query_dsl::QueryDsl::left_join) you need to call
133-
//! [`.nullable()`](crate::expression_methods::NullableExpressionMethods::nullable)
134-
//! on the relevant column in your select clause.
129+
//! executing a query:
130+
//! This error message indicates that you're trying to select a field from a table
131+
//! that does not appear in your from clause. If your query joins the relevant table via
132+
//! [`left_join`](crate::query_dsl::QueryDsl::left_join) you need to call
133+
//! [`.nullable()`](crate::expression_methods::NullableExpressionMethods::nullable)
134+
//! on the relevant column in your select clause.
135135
//!
136136
//!
137137
//! ## Getting help

diesel/src/mysql/connection/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ use crate::RunQueryDsl;
2424
/// `mysql://[user[:password]@]host/database_name[?unix_socket=socket-path&ssl_mode=SSL_MODE*&ssl_ca=/etc/ssl/certs/ca-certificates.crt&ssl_cert=/etc/ssl/certs/client-cert.crt&ssl_key=/etc/ssl/certs/client-key.crt]`
2525
///
2626
///* `host` can be an IP address or a hostname. If it is set to `localhost`, a connection
27-
/// will be attempted through the socket at `/tmp/mysql.sock`. If you want to connect to
28-
/// a local server via TCP (e.g. docker containers), use `0.0.0.0` or `127.0.0.1` instead.
27+
/// will be attempted through the socket at `/tmp/mysql.sock`. If you want to connect to
28+
/// a local server via TCP (e.g. docker containers), use `0.0.0.0` or `127.0.0.1` instead.
2929
/// * `unix_socket` expects the path to the unix socket
3030
/// * `ssl_ca` accepts a path to the system's certificate roots
3131
/// * `ssl_cert` accepts a path to the client's certificate file

diesel/src/pg/connection/raw.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,40 @@ impl RawConnection {
6464
RawResult::new(PQexec(self.internal_connection.as_ptr(), query), self)
6565
}
6666

67+
/// Sends a query and parameters to the server without using the prepare/bind cycle.
68+
///
69+
/// This method uses PQsendQueryParams which combines the prepare and bind steps
70+
/// and is more compatible with connection poolers like PgBouncer.
71+
pub(super) unsafe fn send_query_params(
72+
&self,
73+
query: *const libc::c_char,
74+
param_count: libc::c_int,
75+
param_types: *const Oid,
76+
param_values: *const *const libc::c_char,
77+
param_lengths: *const libc::c_int,
78+
param_formats: *const libc::c_int,
79+
result_format: libc::c_int,
80+
) -> QueryResult<()> {
81+
let res = PQsendQueryParams(
82+
self.internal_connection.as_ptr(),
83+
query,
84+
param_count,
85+
param_types,
86+
param_values,
87+
param_lengths,
88+
param_formats,
89+
result_format,
90+
);
91+
if res == 1 {
92+
Ok(())
93+
} else {
94+
Err(Error::DatabaseError(
95+
DatabaseErrorKind::UnableToSendCommand,
96+
Box::new(self.last_error_message()),
97+
))
98+
}
99+
}
100+
67101
pub(super) unsafe fn send_query_prepared(
68102
&self,
69103
stmt_name: *const libc::c_char,

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

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ use crate::result::QueryResult;
1111

1212
use super::raw::RawConnection;
1313

14+
enum StatementKind {
15+
Unnamed { sql: CString, param_types: Vec<u32> },
16+
Named { name: CString },
17+
}
18+
1419
pub(crate) struct Statement {
15-
name: CString,
20+
kind: StatementKind,
1621
param_formats: Vec<libc::c_int>,
1722
}
1823

@@ -40,16 +45,39 @@ impl Statement {
4045
.len()
4146
.try_into()
4247
.map_err(|e| crate::result::Error::SerializationError(Box::new(e)))?;
43-
unsafe {
44-
raw_connection.send_query_prepared(
45-
self.name.as_ptr(),
46-
param_count,
47-
params_pointer.as_ptr(),
48-
param_lengths.as_ptr(),
49-
self.param_formats.as_ptr(),
50-
1,
51-
)
52-
}?;
48+
49+
match &self.kind {
50+
StatementKind::Named { name } => {
51+
unsafe {
52+
// execute the previously prepared statement
53+
// in autocommit mode, this will be a new transaction
54+
raw_connection.send_query_prepared(
55+
name.as_ptr(),
56+
param_count,
57+
params_pointer.as_ptr(),
58+
param_lengths.as_ptr(),
59+
self.param_formats.as_ptr(),
60+
1,
61+
)
62+
}?
63+
}
64+
StatementKind::Unnamed { sql, param_types } => unsafe {
65+
// execute the unnamed prepared statement using send_query_params
66+
// which internally calls PQsendQueryParams, making sure the
67+
// prepare and execute happens in a single transaction. This
68+
// makes sure these are handled by PgBouncer.
69+
// See https://github.com/diesel-rs/diesel/pull/4539
70+
raw_connection.send_query_params(
71+
sql.as_ptr(),
72+
param_count,
73+
param_types.as_ptr(),
74+
params_pointer.as_ptr(),
75+
param_lengths.as_ptr(),
76+
self.param_formats.as_ptr(),
77+
1,
78+
)
79+
}?,
80+
};
5381
if row_by_row {
5482
raw_connection.enable_row_by_row_mode()?;
5583
}
@@ -62,32 +90,48 @@ impl Statement {
6290
name: Option<&str>,
6391
param_types: &[PgTypeMetadata],
6492
) -> QueryResult<Self> {
65-
let name = CString::new(name.unwrap_or(""))?;
6693
let sql = CString::new(sql)?;
6794
let param_types_vec = param_types
6895
.iter()
6996
.map(|x| x.oid())
7097
.collect::<Result<Vec<_>, _>>()
7198
.map_err(|e| crate::result::Error::SerializationError(Box::new(e)))?;
7299

73-
let internal_result = unsafe {
100+
if let Some(name) = name {
101+
let name = CString::new(name)?;
74102
let param_count: libc::c_int = param_types
75103
.len()
76104
.try_into()
77105
.map_err(|e| crate::result::Error::SerializationError(Box::new(e)))?;
78-
raw_connection.prepare(
79-
name.as_ptr(),
80-
sql.as_ptr(),
81-
param_count,
82-
param_types_to_ptr(Some(&param_types_vec)),
83-
)
84-
};
85-
PgResult::new(internal_result?, raw_connection)?;
106+
let internal_result = unsafe {
107+
raw_connection.prepare(
108+
name.as_ptr(),
109+
sql.as_ptr(),
110+
param_count,
111+
param_types_to_ptr(Some(&param_types_vec)),
112+
)
113+
};
114+
PgResult::new(internal_result?, raw_connection)?;
86115

87-
Ok(Statement {
88-
name,
89-
param_formats: vec![1; param_types.len()],
90-
})
116+
Ok(Statement {
117+
kind: StatementKind::Named { name },
118+
param_formats: vec![1; param_types.len()],
119+
})
120+
} else {
121+
// For unnamed statements, we'll return a Statement object without
122+
// actually preparing it. This allows us to use send_query_params
123+
// later in the execute call. This is needed to better interface
124+
// with PgBouncer which cannot handle unnamed prepared statements
125+
// when those are prepared and executed in separate transactions.
126+
// See https://github.com/diesel-rs/diesel/pull/4539
127+
Ok(Statement {
128+
kind: StatementKind::Unnamed {
129+
sql,
130+
param_types: param_types_vec,
131+
},
132+
param_formats: vec![1; param_types.len()],
133+
})
134+
}
91135
}
92136
}
93137

diesel/src/pg/expression/expression_methods.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -466,12 +466,12 @@ pub trait PgSortExpressionMethods: Sized {
466466
///
467467
/// let asc_default_nulls = nullable_numbers.select(nullable_number)
468468
/// .order(nullable_number.asc())
469-
/// .load(connection)?;
469+
/// .load::<Option<i32>>(connection)?;
470470
/// assert_eq!(vec![Some(1), Some(2), None], asc_default_nulls);
471471
///
472472
/// let asc_nulls_first = nullable_numbers.select(nullable_number)
473473
/// .order(nullable_number.asc().nulls_first())
474-
/// .load(connection)?;
474+
/// .load::<Option<i32>>(connection)?;
475475
/// assert_eq!(vec![None, Some(1), Some(2)], asc_nulls_first);
476476
/// # Ok(())
477477
/// # }
@@ -514,12 +514,12 @@ pub trait PgSortExpressionMethods: Sized {
514514
///
515515
/// let desc_default_nulls = nullable_numbers.select(nullable_number)
516516
/// .order(nullable_number.desc())
517-
/// .load(connection)?;
517+
/// .load::<Option<i32>>(connection)?;
518518
/// assert_eq!(vec![None, Some(2), Some(1)], desc_default_nulls);
519519
///
520520
/// let desc_nulls_last = nullable_numbers.select(nullable_number)
521521
/// .order(nullable_number.desc().nulls_last())
522-
/// .load(connection)?;
522+
/// .load::<Option<i32>>(connection)?;
523523
/// assert_eq!(vec![Some(2), Some(1), None], desc_nulls_last);
524524
/// # Ok(())
525525
/// # }

0 commit comments

Comments
 (0)