From 239238647a8bf67754c33e6dddbc233ee5c6cc56 Mon Sep 17 00:00:00 2001 From: 81reap Date: Fri, 25 Sep 2026 18:38:41 -0400 Subject: [PATCH 1/2] test(database) :: test that parameters reach the DB and are cached Before a bound parameter had to survive a round trip in `tests/transactions` and in `computed_projection_binding_order.sql`, but nothing bound one variable twice in a single statement, and nothing asserted that a parameterized page runs as a prepared statement rather than through `SQLExecDirect`. Now two tests cover both halves of [sqlx-oldapi#57](https://github.com/sqlpage/sqlx-oldapi/issues/57), one of wich covers the `INSERT ... SELECT $x WHERE $x IS NOT NULL` shape from #1474. --- tests/mod.rs | 1 + tests/parameter_binding/echo_parameter.sql | 1 + tests/parameter_binding/mod.rs | 64 +++++++++++++++++++ .../parameter_in_projection.sql | 8 +++ .../parameter_in_projection_mssql.sql | 8 +++ 5 files changed, 82 insertions(+) create mode 100644 tests/parameter_binding/echo_parameter.sql create mode 100644 tests/parameter_binding/mod.rs create mode 100644 tests/parameter_binding/parameter_in_projection.sql create mode 100644 tests/parameter_binding/parameter_in_projection_mssql.sql diff --git a/tests/mod.rs b/tests/mod.rs index e0c0b99a..eca6edb1 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -7,6 +7,7 @@ mod data_formats; mod errors; mod exec; mod oidc; +mod parameter_binding; mod requests; mod server_timing; pub mod sql_test_files; diff --git a/tests/parameter_binding/echo_parameter.sql b/tests/parameter_binding/echo_parameter.sql new file mode 100644 index 00000000..157fa943 --- /dev/null +++ b/tests/parameter_binding/echo_parameter.sql @@ -0,0 +1 @@ +select 'text' as component, max(n) as contents from (select $x as n) as t; diff --git a/tests/parameter_binding/mod.rs b/tests/parameter_binding/mod.rs new file mode 100644 index 00000000..196e409f --- /dev/null +++ b/tests/parameter_binding/mod.rs @@ -0,0 +1,64 @@ +use actix_web::{http::StatusCode, test}; +use sqlx::any::AnyKind; +use sqlx::connection::Connection as _; + +use sqlpage::webserver::database::SupportedDatabase; +use sqlpage::webserver::http::main_handler; + +use crate::common::{get_request_to_with_data, make_app_data}; + +async fn rendered_page( + path: &str, + data: actix_web::web::Data, +) -> actix_web::Result { + let req = get_request_to_with_data(path, data).await?.to_srv_request(); + let resp = main_handler(req).await?; + assert_eq!(resp.status(), StatusCode::OK); + Ok(String::from_utf8(test::read_body(resp).await.to_vec()).unwrap()) +} + +#[actix_web::test] +async fn test_a_parameter_in_a_projection_reaches_the_database() -> actix_web::Result<()> { + let data = make_app_data().await; + let path = match data.db.info.database_type { + SupportedDatabase::Mssql => "/tests/parameter_binding/parameter_in_projection_mssql.sql", + SupportedDatabase::Oracle => return Ok(()), // no CREATE TEMPORARY TABLE + _ => "/tests/parameter_binding/parameter_in_projection.sql", + }; + + let page = rendered_page(&format!("{path}?x=1447"), data).await?; + assert!( + page.contains("1447"), + "{page}\nexpected the bound parameter to reach the inserted row" + ); + Ok(()) +} + +#[actix_web::test] +async fn test_parameterized_pages_leave_a_prepared_statement_in_the_cache() -> actix_web::Result<()> +{ + let data = make_app_data().await; + if data.db.info.kind == AnyKind::Mssql { + return Ok(()); // the MSSQL backend keeps no statement cache + } + + for _ in 0..3 { + let page = rendered_page( + "/tests/parameter_binding/echo_parameter.sql?x=1447", + data.clone(), + ) + .await?; + assert!( + page.contains("1447"), + "{page}\nexpected the bound parameter to reach the query" + ); + } + + let connection = data.db.connection.acquire().await.unwrap(); + assert!( + connection.cached_statements_size() > 0, + "{:?} ran a parameterized query three times without caching a prepared statement", + data.db.info.kind + ); + Ok(()) +} diff --git a/tests/parameter_binding/parameter_in_projection.sql b/tests/parameter_binding/parameter_in_projection.sql new file mode 100644 index 00000000..ac793e5c --- /dev/null +++ b/tests/parameter_binding/parameter_in_projection.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS sqlpage_param_projection; +CREATE TEMPORARY TABLE sqlpage_param_projection(username VARCHAR(100) NOT NULL); + +INSERT INTO sqlpage_param_projection (username) +SELECT $x +WHERE $x IS NOT NULL; + +select 'text' as component, max(username) as contents from sqlpage_param_projection; diff --git a/tests/parameter_binding/parameter_in_projection_mssql.sql b/tests/parameter_binding/parameter_in_projection_mssql.sql new file mode 100644 index 00000000..1626daa1 --- /dev/null +++ b/tests/parameter_binding/parameter_in_projection_mssql.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS #sqlpage_param_projection; +CREATE TABLE #sqlpage_param_projection(username VARCHAR(100) NOT NULL); + +INSERT INTO #sqlpage_param_projection (username) +SELECT $x +WHERE $x IS NOT NULL; + +select 'text' as component, max(username) as contents from #sqlpage_param_projection; From 7c661f1f696311aac510aae2247d2c27883ff49b Mon Sep 17 00:00:00 2001 From: 81reap Date: Mon, 28 Sep 2026 20:56:54 -0400 Subject: [PATCH 2/2] fixup! test(database) :: test that parameters reach the DB and are cached --- tests/parameter_binding/mod.rs | 36 ++++--------------- .../parameter_in_projection.sql | 8 ----- .../parameter_in_projection_mssql.sql | 8 ----- .../parameter_bound_twice_in_a_projection.sql | 9 +++++ 4 files changed, 15 insertions(+), 46 deletions(-) delete mode 100644 tests/parameter_binding/parameter_in_projection.sql delete mode 100644 tests/parameter_binding/parameter_in_projection_mssql.sql create mode 100644 tests/sql_test_files/data/parameter_bound_twice_in_a_projection.sql diff --git a/tests/parameter_binding/mod.rs b/tests/parameter_binding/mod.rs index 196e409f..5fe31a4d 100644 --- a/tests/parameter_binding/mod.rs +++ b/tests/parameter_binding/mod.rs @@ -2,38 +2,10 @@ use actix_web::{http::StatusCode, test}; use sqlx::any::AnyKind; use sqlx::connection::Connection as _; -use sqlpage::webserver::database::SupportedDatabase; use sqlpage::webserver::http::main_handler; use crate::common::{get_request_to_with_data, make_app_data}; -async fn rendered_page( - path: &str, - data: actix_web::web::Data, -) -> actix_web::Result { - let req = get_request_to_with_data(path, data).await?.to_srv_request(); - let resp = main_handler(req).await?; - assert_eq!(resp.status(), StatusCode::OK); - Ok(String::from_utf8(test::read_body(resp).await.to_vec()).unwrap()) -} - -#[actix_web::test] -async fn test_a_parameter_in_a_projection_reaches_the_database() -> actix_web::Result<()> { - let data = make_app_data().await; - let path = match data.db.info.database_type { - SupportedDatabase::Mssql => "/tests/parameter_binding/parameter_in_projection_mssql.sql", - SupportedDatabase::Oracle => return Ok(()), // no CREATE TEMPORARY TABLE - _ => "/tests/parameter_binding/parameter_in_projection.sql", - }; - - let page = rendered_page(&format!("{path}?x=1447"), data).await?; - assert!( - page.contains("1447"), - "{page}\nexpected the bound parameter to reach the inserted row" - ); - Ok(()) -} - #[actix_web::test] async fn test_parameterized_pages_leave_a_prepared_statement_in_the_cache() -> actix_web::Result<()> { @@ -43,11 +15,15 @@ async fn test_parameterized_pages_leave_a_prepared_statement_in_the_cache() -> a } for _ in 0..3 { - let page = rendered_page( + let req = get_request_to_with_data( "/tests/parameter_binding/echo_parameter.sql?x=1447", data.clone(), ) - .await?; + .await? + .to_srv_request(); + let resp = main_handler(req).await?; + assert_eq!(resp.status(), StatusCode::OK); + let page = String::from_utf8(test::read_body(resp).await.to_vec()).unwrap(); assert!( page.contains("1447"), "{page}\nexpected the bound parameter to reach the query" diff --git a/tests/parameter_binding/parameter_in_projection.sql b/tests/parameter_binding/parameter_in_projection.sql deleted file mode 100644 index ac793e5c..00000000 --- a/tests/parameter_binding/parameter_in_projection.sql +++ /dev/null @@ -1,8 +0,0 @@ -DROP TABLE IF EXISTS sqlpage_param_projection; -CREATE TEMPORARY TABLE sqlpage_param_projection(username VARCHAR(100) NOT NULL); - -INSERT INTO sqlpage_param_projection (username) -SELECT $x -WHERE $x IS NOT NULL; - -select 'text' as component, max(username) as contents from sqlpage_param_projection; diff --git a/tests/parameter_binding/parameter_in_projection_mssql.sql b/tests/parameter_binding/parameter_in_projection_mssql.sql deleted file mode 100644 index 1626daa1..00000000 --- a/tests/parameter_binding/parameter_in_projection_mssql.sql +++ /dev/null @@ -1,8 +0,0 @@ -DROP TABLE IF EXISTS #sqlpage_param_projection; -CREATE TABLE #sqlpage_param_projection(username VARCHAR(100) NOT NULL); - -INSERT INTO #sqlpage_param_projection (username) -SELECT $x -WHERE $x IS NOT NULL; - -select 'text' as component, max(username) as contents from #sqlpage_param_projection; diff --git a/tests/sql_test_files/data/parameter_bound_twice_in_a_projection.sql b/tests/sql_test_files/data/parameter_bound_twice_in_a_projection.sql new file mode 100644 index 00000000..66d458ea --- /dev/null +++ b/tests/sql_test_files/data/parameter_bound_twice_in_a_projection.sql @@ -0,0 +1,9 @@ +-- One variable bound twice in a single statement, in the projection rather than +-- only in the predicate. https://github.com/sqlpage/SQLPage/issues/1474 +drop table if exists parameter_bound_twice_t; +create table parameter_bound_twice_t(id int primary key); +insert into parameter_bound_twice_t (id) values (1); + +select 'It works !' as expected, + case when $x is not null and $x = '1' then 'It works !' else 'fail' end as actual +from parameter_bound_twice_t;