refactor: continue
This commit is contained in:
parent
d0a423df9f
commit
a3b9543d0e
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
|
@ -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"),
|
||||||
|
Loading…
Reference in New Issue
Block a user