use std::fmt::Write;
use crate::types::{DatabaseType, sql_hints::OrderByClause};
pub fn append_order_by(
sql: &mut String,
order_by: Option<&[OrderByClause]>,
db_type: DatabaseType,
) -> crate::Result<bool> {
let Some(clauses) = order_by.filter(|c| !c.is_empty()) else {
return Ok(false);
};
sql.push_str(" ORDER BY ");
for (i, clause) in clauses.iter().enumerate() {
OrderByClause::validate_field_name(&clause.field)?;
if i > 0 {
sql.push_str(", ");
}
let expr = if let Some(ref col) = clause.native_column {
col.clone()
} else {
let key = clause.storage_key();
db_type.typed_json_field_expr(&key, clause.field_type)
};
write!(sql, "{expr} {}", clause.direction.as_sql()).expect("write to String is infallible");
}
Ok(true)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::types::sql_hints::OrderDirection;
#[test]
fn test_append_order_by_none() {
let mut sql = "SELECT data FROM v_user".to_string();
let appended = append_order_by(&mut sql, None, DatabaseType::PostgreSQL).unwrap();
assert!(!appended);
assert!(!sql.contains("ORDER BY"));
}
#[test]
fn test_append_order_by_empty() {
let mut sql = "SELECT data FROM v_user".to_string();
let appended = append_order_by(&mut sql, Some(&[]), DatabaseType::PostgreSQL).unwrap();
assert!(!appended);
assert!(!sql.contains("ORDER BY"));
}
#[test]
fn test_append_order_by_single_clause_postgres() {
let mut sql = "SELECT data FROM v_user".to_string();
let clauses = [OrderByClause::new(
"createdAt".to_string(),
OrderDirection::Desc,
)];
let appended = append_order_by(&mut sql, Some(&clauses), DatabaseType::PostgreSQL).unwrap();
assert!(appended);
assert_eq!(sql, "SELECT data FROM v_user ORDER BY data->>'created_at' DESC");
}
#[test]
fn test_append_order_by_multiple_clauses_postgres() {
let mut sql = "SELECT data FROM v_user".to_string();
let clauses = [
OrderByClause::new("lastName".to_string(), OrderDirection::Asc),
OrderByClause::new("createdAt".to_string(), OrderDirection::Desc),
];
let appended = append_order_by(&mut sql, Some(&clauses), DatabaseType::PostgreSQL).unwrap();
assert!(appended);
assert_eq!(
sql,
"SELECT data FROM v_user ORDER BY data->>'last_name' ASC, data->>'created_at' DESC"
);
}
#[test]
fn test_append_order_by_mysql() {
let mut sql = "SELECT data FROM v_user".to_string();
let clauses = [OrderByClause::new(
"firstName".to_string(),
OrderDirection::Asc,
)];
let appended = append_order_by(&mut sql, Some(&clauses), DatabaseType::MySQL).unwrap();
assert!(appended);
assert_eq!(
sql,
"SELECT data FROM v_user ORDER BY JSON_UNQUOTE(JSON_EXTRACT(data, '$.first_name')) ASC"
);
}
#[test]
fn test_append_order_by_sqlite() {
let mut sql = "SELECT data FROM v_user".to_string();
let clauses = [OrderByClause::new(
"firstName".to_string(),
OrderDirection::Asc,
)];
let appended = append_order_by(&mut sql, Some(&clauses), DatabaseType::SQLite).unwrap();
assert!(appended);
assert_eq!(sql, "SELECT data FROM v_user ORDER BY json_extract(data, '$.first_name') ASC");
}
#[test]
fn test_append_order_by_sqlserver() {
let mut sql = "SELECT data FROM v_user".to_string();
let clauses = [OrderByClause::new(
"firstName".to_string(),
OrderDirection::Desc,
)];
let appended = append_order_by(&mut sql, Some(&clauses), DatabaseType::SQLServer).unwrap();
assert!(appended);
assert_eq!(sql, "SELECT data FROM v_user ORDER BY JSON_VALUE(data, '$.first_name') DESC");
}
#[test]
fn test_append_order_by_invalid_field_name() {
let mut sql = "SELECT data FROM v_user".to_string();
let clauses = [OrderByClause::new(
"field'; DROP TABLE users; --".to_string(),
OrderDirection::Asc,
)];
let result = append_order_by(&mut sql, Some(&clauses), DatabaseType::PostgreSQL);
assert!(result.is_err());
}
#[test]
fn test_append_order_by_snake_case_passthrough() {
let mut sql = "SELECT data FROM v_user".to_string();
let clauses = [OrderByClause::new("id".to_string(), OrderDirection::Asc)];
let appended = append_order_by(&mut sql, Some(&clauses), DatabaseType::PostgreSQL).unwrap();
assert!(appended);
assert_eq!(sql, "SELECT data FROM v_user ORDER BY data->>'id' ASC");
}
#[test]
fn test_append_order_by_numeric_cast_postgres() {
use crate::types::sql_hints::OrderByFieldType;
let mut sql = "SELECT data FROM v_order".to_string();
let mut clause = OrderByClause::new("totalAmount".to_string(), OrderDirection::Desc);
clause.field_type = OrderByFieldType::Numeric;
let appended =
append_order_by(&mut sql, Some(&[clause]), DatabaseType::PostgreSQL).unwrap();
assert!(appended);
assert_eq!(sql, "SELECT data FROM v_order ORDER BY (data->>'total_amount')::numeric DESC");
}
#[test]
fn test_append_order_by_integer_cast_mysql() {
use crate::types::sql_hints::OrderByFieldType;
let mut sql = "SELECT data FROM v_order".to_string();
let mut clause = OrderByClause::new("quantity".to_string(), OrderDirection::Asc);
clause.field_type = OrderByFieldType::Integer;
let appended = append_order_by(&mut sql, Some(&[clause]), DatabaseType::MySQL).unwrap();
assert!(appended);
assert_eq!(
sql,
"SELECT data FROM v_order ORDER BY CAST(JSON_UNQUOTE(JSON_EXTRACT(data, '$.quantity')) AS SIGNED) ASC"
);
}
#[test]
fn test_append_order_by_datetime_cast_postgres() {
use crate::types::sql_hints::OrderByFieldType;
let mut sql = "SELECT data FROM v_event".to_string();
let mut clause = OrderByClause::new("createdAt".to_string(), OrderDirection::Desc);
clause.field_type = OrderByFieldType::DateTime;
let appended =
append_order_by(&mut sql, Some(&[clause]), DatabaseType::PostgreSQL).unwrap();
assert!(appended);
assert_eq!(
sql,
"SELECT data FROM v_event ORDER BY (data->>'created_at')::timestamptz DESC"
);
}
#[test]
fn test_append_order_by_native_column() {
let mut sql = "SELECT data FROM tv_user".to_string();
let clause = OrderByClause {
field: "createdAt".to_string(),
direction: OrderDirection::Desc,
field_type: crate::types::sql_hints::OrderByFieldType::DateTime,
native_column: Some("created_at".to_string()),
};
let appended =
append_order_by(&mut sql, Some(&[clause]), DatabaseType::PostgreSQL).unwrap();
assert!(appended);
assert_eq!(sql, "SELECT data FROM tv_user ORDER BY created_at DESC");
}
#[test]
fn test_append_order_by_mixed_native_and_jsonb() {
use crate::types::sql_hints::OrderByFieldType;
let mut sql = "SELECT data FROM tv_user".to_string();
let clauses = [
OrderByClause {
field: "createdAt".to_string(),
direction: OrderDirection::Desc,
field_type: OrderByFieldType::DateTime,
native_column: Some("created_at".to_string()),
},
{
let mut c = OrderByClause::new("name".to_string(), OrderDirection::Asc);
c.field_type = OrderByFieldType::Text;
c
},
];
let appended = append_order_by(&mut sql, Some(&clauses), DatabaseType::PostgreSQL).unwrap();
assert!(appended);
assert_eq!(sql, "SELECT data FROM tv_user ORDER BY created_at DESC, data->>'name' ASC");
}
}