refactor: continue

This commit is contained in:
master 2025-05-16 01:02:17 +08:00
parent d0a423df9f
commit a3b9543d0e
3 changed files with 97 additions and 43 deletions

1
Cargo.lock generated
View File

@ -5150,6 +5150,7 @@ dependencies = [
"reqwest_cookie_store", "reqwest_cookie_store",
"rss", "rss",
"rstest", "rstest",
"rust_decimal",
"scraper", "scraper",
"sea-orm", "sea-orm",
"sea-orm-migration", "sea-orm-migration",

View File

@ -116,6 +116,7 @@ downloader = { workspace = true }
util = { workspace = true } util = { workspace = true }
fetch = { workspace = true } fetch = { workspace = true }
nanoid = "0.4.0" nanoid = "0.4.0"
rust_decimal = "1.37.1"
[dev-dependencies] [dev-dependencies]
serial_test = "3" serial_test = "3"

View File

@ -1,5 +1,6 @@
use async_graphql::dynamic::SchemaError; use async_graphql::dynamic::SchemaError;
use itertools::Itertools; use itertools::Itertools;
use rust_decimal::{Decimal, prelude::FromPrimitive};
use sea_orm::{ use sea_orm::{
Condition, Condition,
sea_query::{ sea_query::{
@ -231,34 +232,59 @@ impl JsonPath {
} }
} }
fn build_json_path_expr(path: &JsonPath) -> SimpleExpr { fn json_path_expr(path: &JsonPath) -> SimpleExpr {
Expr::val(path.join()).into() Expr::val(path.join()).into()
} }
fn build_json_path_exists_expr(col_expr: impl Into<SimpleExpr>, path: &JsonPath) -> SimpleExpr { fn json_path_exists_expr(col_expr: impl Into<SimpleExpr>, path: &JsonPath) -> SimpleExpr {
Expr::cust_with_exprs( Expr::cust_with_exprs(
"JSON_EXISTS($1, $2)", "JSON_EXISTS($1, $2)",
[col_expr.into(), build_json_path_expr(path)], [col_expr.into(), json_path_expr(path)],
) )
} }
fn build_json_path_query_expr(col_expr: impl Into<SimpleExpr>, path: &JsonPath) -> SimpleExpr { fn json_path_query_expr(col_expr: impl Into<SimpleExpr>, path: &JsonPath) -> SimpleExpr {
Expr::cust_with_exprs( Expr::cust_with_exprs(
"jsonb_path_query($1, $2)", "jsonb_path_query($1, $2)",
[col_expr.into(), build_json_path_expr(path)], [col_expr.into(), json_path_expr(path)],
) )
} }
fn build_json_value_is_in_values_expr( fn json_path_query_first_expr(col_expr: impl Into<SimpleExpr>, path: &JsonPath) -> SimpleExpr {
Expr::cust_with_exprs(
"jsonb_path_query_first($1, $2)",
[col_expr.into(), json_path_expr(path)],
)
}
fn json_path_query_first_cast_expr(
col_expr: impl Into<SimpleExpr>,
path: &JsonPath,
value: &JsonValue,
) -> RecorderResult<SimpleExpr> {
let cast_target = match value {
JsonValue::Number(..) => "numeric",
JsonValue::Bool(..) => "boolean",
JsonValue::String(..) => "text",
_ => {
return Err(SchemaError(
"JsonFilterInput leaf can not be only be casted to numeric, boolean or text"
.to_string(),
))?;
}
};
Ok(json_path_query_first_expr(col_expr, path).cast_as(cast_target))
}
fn json_path_is_in_values_expr(
col_expr: impl Into<SimpleExpr>, col_expr: impl Into<SimpleExpr>,
path: &JsonPath, path: &JsonPath,
values: Vec<JsonValue>, values: Vec<JsonValue>,
) -> SimpleExpr { ) -> SimpleExpr {
Expr::cust_with_exprs( Expr::cust_with_exprs(
"jsonb_path_query($1, $2) = ANY($3)", "$1 = ANY($2)",
[ [
col_expr.into(), json_path_query_expr(col_expr, path),
build_json_path_expr(path),
Expr::val(DbValue::Array( Expr::val(DbValue::Array(
ArrayType::Json, ArrayType::Json,
Some(Box::new( Some(Box::new(
@ -273,37 +299,38 @@ fn build_json_value_is_in_values_expr(
) )
} }
fn build_json_leaf_cast_expr( fn json_path_eq_expr(
expr: impl Into<SimpleExpr>,
path: &[&str],
) -> RecorderResult<SimpleExpr> {
if path.is_empty() {
Err(async_graphql::dynamic::SchemaError(
"JsonFilterInput path must be at least one level deep".to_string(),
))?
}
let mut expr = expr.into();
for key in path.iter().take(path.len() - 1) {
expr = expr.get_json_field(*key);
}
expr = expr.cast_json_field(path[path.len() - 1]);
Ok(expr)
}
fn build_json_path_eq_expr(
col_expr: impl Into<SimpleExpr>, col_expr: impl Into<SimpleExpr>,
path: &JsonPath, path: &JsonPath,
value: JsonValue, value: JsonValue,
) -> SimpleExpr { ) -> SimpleExpr {
build_json_path_query_expr(col_expr, path).eq(value) json_path_query_expr(col_expr, path).eq(value)
} }
fn build_json_path_ne_expr( fn json_path_ne_expr(
col_expr: impl Into<SimpleExpr>, col_expr: impl Into<SimpleExpr>,
path: &JsonPath, path: &JsonPath,
value: JsonValue, value: JsonValue,
) -> SimpleExpr { ) -> SimpleExpr {
build_json_path_query_expr(col_expr, path).ne(value) json_path_query_expr(col_expr, path).ne(value)
}
fn convert_json_number_to_db_decimal(json_number: serde_json::Number) -> RecorderResult<Decimal> {
if let Some(f) = json_number.as_f64() {
let decimal = Decimal::from_f64(f).ok_or_else(|| {
SchemaError("JsonFilterInput leaf value failed to convert to decimal".to_string())
})?;
Ok(decimal)
} else if let Some(i) = json_number.as_i64() {
Ok(Decimal::from(i))
} else if let Some(u) = json_number.as_u64() {
Ok(Decimal::from(u))
} else {
Err(
SchemaError("JsonFilterInput leaf value failed to convert to a number".to_string())
.into(),
)
}
} }
pub fn prepare_json_leaf_condition( pub fn prepare_json_leaf_condition(
@ -317,7 +344,7 @@ pub fn prepare_json_leaf_condition(
op @ (JsonFilterOperation::Exists | JsonFilterOperation::NotExists), op @ (JsonFilterOperation::Exists | JsonFilterOperation::NotExists),
JsonValue::Bool(exists), JsonValue::Bool(exists),
) => { ) => {
let json_exists_expr = build_json_path_exists_expr(col_expr, path); let json_exists_expr = json_path_exists_expr(col_expr, path);
if (op == JsonFilterOperation::Exists && exists) if (op == JsonFilterOperation::Exists && exists)
|| (op == JsonFilterOperation::NotExists && !exists) || (op == JsonFilterOperation::NotExists && !exists)
{ {
@ -334,16 +361,16 @@ pub fn prepare_json_leaf_condition(
unreachable!("JsonFilterInput leaf can not be $and or $or with any value") unreachable!("JsonFilterInput leaf can not be $and or $or with any value")
} }
(JsonFilterOperation::Equals, value) => { (JsonFilterOperation::Equals, value) => {
build_json_path_eq_expr(col_expr, path, value).into_condition() json_path_eq_expr(col_expr, path, value).into_condition()
} }
(JsonFilterOperation::NotEquals, value) => { (JsonFilterOperation::NotEquals, value) => {
build_json_path_ne_expr(col_expr, path, value).into_condition() json_path_ne_expr(col_expr, path, value).into_condition()
} }
( (
op @ (JsonFilterOperation::IsIn | JsonFilterOperation::IsNotIn), op @ (JsonFilterOperation::IsIn | JsonFilterOperation::IsNotIn),
JsonValue::Array(values), JsonValue::Array(values),
) => { ) => {
let expr = build_json_value_is_in_values_expr(col_expr, path, values.clone()); let expr = json_path_is_in_values_expr(col_expr, path, values.clone());
if op == JsonFilterOperation::IsIn { if op == JsonFilterOperation::IsIn {
expr.into_condition() expr.into_condition()
} else { } else {
@ -358,7 +385,7 @@ pub fn prepare_json_leaf_condition(
op @ (JsonFilterOperation::IsNull | JsonFilterOperation::IsNotNull), op @ (JsonFilterOperation::IsNull | JsonFilterOperation::IsNotNull),
JsonValue::Bool(is), JsonValue::Bool(is),
) => { ) => {
let expr = build_json_path_query_expr(col_expr, path); let expr = json_path_query_expr(col_expr, path);
if op == JsonFilterOperation::IsNull { if op == JsonFilterOperation::IsNull {
if is { if is {
expr.is_null().into_condition() expr.is_null().into_condition()
@ -373,14 +400,39 @@ pub fn prepare_json_leaf_condition(
} }
} }
} }
(
op @ (JsonFilterOperation::GreaterThan
| JsonFilterOperation::LessThan
| JsonFilterOperation::GreaterThanEquals
| JsonFilterOperation::LessThanEquals),
v @ (JsonValue::Number(..) | JsonValue::Bool(..) | JsonValue::String(..)),
) => {
let lexpr = json_path_query_first_cast_expr(col_expr, path, &v)?;
let rexpr: SimpleExpr = match v {
JsonValue::Number(n) => Expr::val(DbValue::Decimal(Some(Box::new(
convert_json_number_to_db_decimal(n)?,
))))
.into(),
JsonValue::Bool(b) => Expr::val(b).into(),
JsonValue::String(s) => Expr::val(s).into(),
_ => unreachable!(),
};
match op {
JsonFilterOperation::GreaterThan => lexpr.gt(rexpr).into_condition(),
JsonFilterOperation::GreaterThanEquals => lexpr.gte(rexpr).into_condition(),
JsonFilterOperation::LessThan => lexpr.lt(rexpr).into_condition(),
JsonFilterOperation::LessThanEquals => lexpr.lte(rexpr).into_condition(),
_ => unreachable!(),
}
}
( (
JsonFilterOperation::GreaterThan JsonFilterOperation::GreaterThan
| JsonFilterOperation::GreaterThanEquals | JsonFilterOperation::GreaterThanEquals
| JsonFilterOperation::LessThan | JsonFilterOperation::LessThan
| JsonFilterOperation::LessThanEquals, | JsonFilterOperation::LessThanEquals,
JsonValue::Array(_), _,
) => Err(SchemaError(format!( ) => Err(SchemaError(format!(
"JsonFilterInput leaf can not be {} with an array", "JsonFilterInput leaf can not be {} with an array, object or null",
op.as_ref() op.as_ref()
)))?, )))?,
_ => todo!(), _ => todo!(),
@ -516,8 +568,8 @@ mod tests {
} }
#[test] #[test]
fn test_build_json_path_exists_expr() { fn test_json_path_exists_expr() {
let (sql, params) = build_test_query_sql(build_json_path_exists_expr( let (sql, params) = build_test_query_sql(json_path_exists_expr(
Expr::col((TestTable::Table, TestTable::Job)), Expr::col((TestTable::Table, TestTable::Job)),
&build_test_json_path(&["a", "b", "c"]), &build_test_json_path(&["a", "b", "c"]),
)); ));
@ -530,8 +582,8 @@ mod tests {
} }
#[test] #[test]
fn test_build_json_path_query_expr() -> RecorderResult<()> { fn test_json_path_query_expr() -> RecorderResult<()> {
let (sql, params) = build_test_query_sql(build_json_value_is_in_values_expr( let (sql, params) = build_test_query_sql(json_path_is_in_values_expr(
Expr::col((TestTable::Table, TestTable::Job)), Expr::col((TestTable::Table, TestTable::Job)),
&build_test_json_path(&["a", "b", "c"]), &build_test_json_path(&["a", "b", "c"]),
vec![json!(1), json!("str"), json!(true)], vec![json!(1), json!("str"), json!(true)],
@ -550,8 +602,8 @@ mod tests {
} }
#[test] #[test]
fn test_build_json_path_eq_expr() -> RecorderResult<()> { fn test_json_path_eq_expr() -> RecorderResult<()> {
let (sql, params) = build_test_query_sql(build_json_path_eq_expr( let (sql, params) = build_test_query_sql(json_path_eq_expr(
Expr::col((TestTable::Table, TestTable::Job)), Expr::col((TestTable::Table, TestTable::Job)),
&build_test_json_path(&["a", "b", "c"]), &build_test_json_path(&["a", "b", "c"]),
json!("str"), json!("str"),