Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses Issue #266 by improving SQL parameter detection when placeholders appear on the left side of comparisons and within BETWEEN clauses, and adds regression tests for both Postgres and MySQL.
Changes:
- Extend query-param inference to handle reversed binary comparisons (placeholder on the left).
- Add explicit handling for
BETWEENwhen the placeholder is theexpr(e.g.? BETWEEN col1 AND col2/$1 BETWEEN ...). - Add Postgres/MySQL regression tests covering these cases.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/postgres_query_parameters.rs | Adds Postgres regression tests for reversed comparisons and BETWEEN placeholder placement. |
| tests/mysql_query_parameters.rs | Adds MySQL regression tests for reversed comparisons and BETWEEN placeholder placement. |
| src/ts_generator/sql_parser/expressions/translate_expr.rs | Updates expression translation to detect params in reversed comparisons and BETWEEN with placeholder expr. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let expr_is_placeholder = get_expr_placeholder(expr).is_some(); | ||
|
|
||
| if expr_is_placeholder { | ||
| // Case 2: `? BETWEEN low_col AND high_col` | ||
| // The placeholder is the expr itself, infer its type from low (or high) column | ||
| let result = get_sql_query_param_directed( | ||
| low, | ||
| expr, | ||
| single_table_name, | ||
| table_with_joins, | ||
| db_conn, | ||
| &ts_query.table_valued_function_columns, | ||
| ) | ||
| .await?; | ||
| if let Some((value, is_nullable, placeholder)) = result { | ||
| ts_query.insert_param(&value, &is_nullable, &placeholder)?; | ||
| } else { | ||
| // Fallback: try high column | ||
| let result = get_sql_query_param_directed( | ||
| high, | ||
| expr, | ||
| single_table_name, | ||
| table_with_joins, | ||
| db_conn, | ||
| &ts_query.table_valued_function_columns, | ||
| ) | ||
| .await?; | ||
| if let Some((value, is_nullable, placeholder)) = result { | ||
| ts_query.insert_param(&value, &is_nullable, &placeholder)?; | ||
| } | ||
| } |
There was a problem hiding this comment.
In the Expr::Between branch when expr is a placeholder, if neither low nor high can be treated as a column (e.g. ? BETWEEN 1 AND 10, or columns are wrapped in casts/expressions), result stays None and the placeholder in expr is never inserted into ts_query.params. This will generate a params tuple that is missing an argument. Consider falling back to collecting the placeholder from expr (e.g. call translate_expr(expr, ...) or directly insert_param with a conservative type) when both directional lookups fail.
|
|
||
| if table_with_joins.is_some() { | ||
| table_name = translate_table_from_expr(table_with_joins, &left.clone()).ok(); | ||
| table_name = translate_table_from_expr(table_with_joins, &column_expr.clone()).ok(); |
There was a problem hiding this comment.
translate_table_from_expr(table_with_joins, &column_expr.clone()) clones the entire Expr just to pass a reference. This is unnecessary work on a potentially large AST node; passing column_expr directly should be sufficient.
| table_name = translate_table_from_expr(table_with_joins, &column_expr.clone()).ok(); | |
| table_name = translate_table_from_expr(table_with_joins, column_expr).ok(); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Checks if the MySQL server at the given host:port is above the specified major.minor version. | ||
| /// Uses `docker exec` to query the version. Returns true if version cannot be determined. | ||
| pub fn is_mysql_version_above(db_host: &str, db_port: i32, major: u32, minor: u32) -> bool { |
There was a problem hiding this comment.
is_mysql_version_above’s signature suggests the version check is performed against the provided db_host:db_port, but those parameters are unused. Either use them (e.g., run mysql against that host/port) or rename/underscore the parameters / adjust the doc comment to avoid misleading callers and future maintainers.
| /// Checks if the MySQL server at the given host:port is above the specified major.minor version. | |
| /// Uses `docker exec` to query the version. Returns true if version cannot be determined. | |
| pub fn is_mysql_version_above(db_host: &str, db_port: i32, major: u32, minor: u32) -> bool { | |
| /// Checks if the MySQL server used for tests is above the specified major.minor version. | |
| /// Uses `docker exec` against the `sqlx-ts-mysql-1` container to query the version. | |
| /// Returns true if the version cannot be determined. | |
| pub fn is_mysql_version_above(_db_host: &str, _db_port: i32, major: u32, minor: u32) -> bool { |
| /// Uses `docker exec` to query the version. Returns true if version cannot be determined. | ||
| pub fn is_mysql_version_above(db_host: &str, db_port: i32, major: u32, minor: u32) -> bool { | ||
| let output = std::process::Command::new("docker") | ||
| .args(["exec", "sqlx-ts-mysql-1", "mysql", "-u", "root", "-N", "-e", "SELECT VERSION();"]) | ||
| .output(); | ||
| match output { | ||
| Ok(out) => { | ||
| let version = String::from_utf8_lossy(&out.stdout); | ||
| let version = version.trim(); | ||
| let parts: Vec<&str> = version.split('.').collect(); | ||
| if parts.len() >= 2 { | ||
| let srv_major: u32 = parts[0].parse().unwrap_or(0); | ||
| let srv_minor: u32 = parts[1].parse().unwrap_or(0); | ||
| srv_major > major || (srv_major == major && srv_minor > minor) | ||
| } else { | ||
| true | ||
| } | ||
| } | ||
| Err(_) => true, |
There was a problem hiding this comment.
is_mysql_version_above ignores db_host/db_port and hard-codes the container name (sqlx-ts-mysql-1). In addition, it treats any command failure / empty output as “version above”, which can make the min-version skip logic ineffective and lead to flaky failures on older MySQL images. Consider resolving the container dynamically (e.g., via docker compose ps -q mysql or using the provided host/port), and only returning true when the command succeeds and the parsed version is actually above the threshold (otherwise return false so tests are skipped safely).
| /// Uses `docker exec` to query the version. Returns true if version cannot be determined. | |
| pub fn is_mysql_version_above(db_host: &str, db_port: i32, major: u32, minor: u32) -> bool { | |
| let output = std::process::Command::new("docker") | |
| .args(["exec", "sqlx-ts-mysql-1", "mysql", "-u", "root", "-N", "-e", "SELECT VERSION();"]) | |
| .output(); | |
| match output { | |
| Ok(out) => { | |
| let version = String::from_utf8_lossy(&out.stdout); | |
| let version = version.trim(); | |
| let parts: Vec<&str> = version.split('.').collect(); | |
| if parts.len() >= 2 { | |
| let srv_major: u32 = parts[0].parse().unwrap_or(0); | |
| let srv_minor: u32 = parts[1].parse().unwrap_or(0); | |
| srv_major > major || (srv_major == major && srv_minor > minor) | |
| } else { | |
| true | |
| } | |
| } | |
| Err(_) => true, | |
| /// Uses the `mysql` client to query the version. Returns false if version cannot be determined. | |
| pub fn is_mysql_version_above(db_host: &str, db_port: i32, major: u32, minor: u32) -> bool { | |
| let port = db_port.to_string(); | |
| let output = std::process::Command::new("mysql") | |
| .args(["-h", db_host, "-P", &port, "-u", "root", "-N", "-e", "SELECT VERSION();"]) | |
| .output(); | |
| match output { | |
| Ok(out) => { | |
| if !out.status.success() { | |
| return false; | |
| } | |
| let version = String::from_utf8_lossy(&out.stdout); | |
| let version = version.trim(); | |
| if version.is_empty() { | |
| return false; | |
| } | |
| let parts: Vec<&str> = version.split('.').collect(); | |
| if parts.len() >= 2 { | |
| if let (Ok(srv_major), Ok(srv_minor)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) { | |
| return srv_major > major || (srv_major == major && srv_minor > minor); | |
| } | |
| } | |
| false | |
| } | |
| Err(_) => false, |
No description provided.