如何在Rust的sqlx库query!宏中动态插入列名实现动态排序
问题根因
你当前的写法不生效是因为PostgreSQL的预编译参数绑定(即$1这类占位符)仅支持传入查询值,无法用于传入表名、列名这类SQL标识符,你传入的metric会被视为普通字符串常量,最终查询相当于按固定字符串排序,自然不会生效。
方案1:匹配固定列(推荐,安全性最高)
如果允许排序的列数量不多,直接用match匹配不同列,调用写死的SQL语句,可以保留SQLx编译时SQL校验的能力,完全规避SQL注入风险:
pub async fn fetch_tweets_by_metric( pool: &PgPool, metric: &str, ) -> Result<Vec<Tweet>, sqlx::error::Error> { let tweets = match metric { // 这里列出所有允许排序的列,对应写死ORDER BY的列名 "likes" => sqlx::query_as!( Tweet, r#"SELECT * FROM tweets ORDER BY likes DESC"# ) .fetch_all(pool) .await?, "retweet_count" => sqlx::query_as!( Tweet, r#"SELECT * FROM tweets ORDER BY retweet_count DESC"# ) .fetch_all(pool) .await?, "created_at" => sqlx::query_as!( Tweet, r#"SELECT * FROM tweets ORDER BY created_at DESC"# ) .fetch_all(pool) .await?, // 非法列名直接返回错误,也可以替换为自定义的非法参数错误 _ => return Err(sqlx::error::Error::RowNotFound), }; Ok(tweets) }
方案2:动态拼接SQL加白名单校验(灵活)
如果允许排序的列较多,不想写冗余的匹配分支,可以先对传入的metric做白名单校验,确认是合法列名后再动态拼接SQL,注意此时要使用不带!的普通query_as方法(带!的宏是编译时校验,不支持动态SQL):
pub async fn fetch_tweets_by_metric( pool: &PgPool, metric: &str, ) -> Result<Vec<Tweet>, sqlx::error::Error> { // 白名单:所有允许排序的列名,根据实际业务调整 const ALLOWED_SORT_COLUMNS: &[&str] = &["likes", "retweet_count", "created_at", "view_count"]; if !ALLOWED_SORT_COLUMNS.contains(&metric) { return Err(sqlx::error::Error::RowNotFound); } // 校验通过后拼接SQL let query_sql = format!( r#"SELECT * FROM tweets ORDER BY {} DESC"#, metric ); let tweets = sqlx::query_as::<_, Tweet>(&query_sql) .fetch_all(pool) .await?; Ok(tweets) }
注意:绝对不能跳过白名单校验直接拼接用户输入的内容到SQL语句中,否则会存在严重的SQL注入漏洞,导致数据泄露或损坏。
内容的提问来源于stack exchange,提问作者ilmoi
相关产品推荐
相关产品推荐

