fix(mysql): validate parameter count for prepared statements (#3857)

* fix(mysql): validate parameter count for prepared statements

Add validation to ensure the number of provided parameters matches the
expected count for MySQL prepared statements.
This prevents protocol errors by returning an error if the counts do not match before sending
the statement for execution.

* refactor(mysql): use err_protocol macro for error creation

Replace direct Error::Protocol(format!()) calls with err_protocol!
macro in MySQL connection executor.

* test(mysql): add parameter count validation tests

- Add test for too few parameters provided to query
- Add test for too many parameters provided to query
- Add test for parameters provided when none expected
- All tests verify Error::Protocol is returned for mismatches

Covers cases for issue #3774 parameter validation fix.
This commit is contained in:
Aleh Shapo 2025-07-03 02:24:44 +02:00 committed by GitHub
parent 8ebc6f9427
commit 29549b14c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 67 additions and 0 deletions

View File

@ -124,6 +124,16 @@ impl MySqlConnection {
.get_or_prepare_statement(sql)
.await?;
if arguments.types.len() != metadata.parameters {
return Err(
err_protocol!(
"prepared statement expected {} parameters but {} parameters were provided",
metadata.parameters,
arguments.types.len()
)
);
}
// https://dev.mysql.com/doc/internals/en/com-stmt-execute.html
self.inner.stream
.send_packet(StatementExecute {
@ -138,6 +148,16 @@ impl MySqlConnection {
.prepare_statement(sql)
.await?;
if arguments.types.len() != metadata.parameters {
return Err(
err_protocol!(
"prepared statement expected {} parameters but {} parameters were provided",
metadata.parameters,
arguments.types.len()
)
);
}
// https://dev.mysql.com/doc/internals/en/com-stmt-execute.html
self.inner.stream
.send_packet(StatementExecute {

View File

@ -100,3 +100,50 @@ async fn it_fails_with_invalid_save_point_statement() -> anyhow::Result<()> {
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_parameter_count_mismatch_too_few() -> anyhow::Result<()> {
let mut conn = new::<MySql>().await?;
let res: Result<_, sqlx::Error> =
sqlx::query("SELECT * FROM tweet WHERE id = ? AND owner_id = ?")
.bind(1_i64)
.execute(&mut conn)
.await;
let err = res.unwrap_err();
assert!(matches!(err, Error::Protocol(_)), "{err}");
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_parameter_count_mismatch_too_many() -> anyhow::Result<()> {
let mut conn = new::<MySql>().await?;
let res: Result<_, sqlx::Error> = sqlx::query("SELECT * FROM tweet WHERE id = ?")
.bind(1_i64)
.bind(2_i64)
.execute(&mut conn)
.await;
let err = res.unwrap_err();
assert!(matches!(err, Error::Protocol(_)), "{err}");
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_parameter_count_mismatch_zero_expected() -> anyhow::Result<()> {
let mut conn = new::<MySql>().await?;
let res: Result<_, sqlx::Error> = sqlx::query("SELECT COUNT(*) FROM tweet")
.bind(1_i64)
.execute(&mut conn)
.await;
let err = res.unwrap_err();
assert!(matches!(err, Error::Protocol(_)), "{err}");
Ok(())
}