From 6d129ce4f6d98b043d34a9a3dc5631ad64461670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20C=C3=A1mara?= Date: Fri, 15 Nov 2024 11:16:34 +0100 Subject: [PATCH] Add Column Casting Support to Filter Nodes --- CHANGELOG.md | 25 ++++++++++++++---------- Cargo.toml | 6 +++--- modql-macros/src/derives_filter/mod.rs | 7 +++++++ modql-macros/src/derives_filter/utils.rs | 6 +++++- src/filter/nodes/node.rs | 1 + src/filter/ops/op_val_string.rs | 11 +++++++---- src/filter/ops/op_val_value.rs | 16 +++++---------- src/sea_utils.rs | 11 ++++++++++- tests/test_expand_filter_nodes.rs | 3 ++- 9 files changed, 55 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f34d43..d182fb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,16 @@ `.` minor | `+` Addition | `^` improvement | `!` Change | `*` Refactor +## 2024-11-15 - `0.4.1-WIP` + +- `^` Update `sea-query` and `rustsqlite` to version `0.32` +- `!` Remove `cast_column_as` from filter, it's now on field + ## 2024-09-23 - `0.4.0` - `.` update to sea-query-rusqlite 0.6 - `.` add rustfmt.toml -- `^` Update sea-query version `0.31 +- `^` Update sea-query version `0.31` - `+` Add CaseInsensitive for StringOpVals (`StartsWithCt` .. ) - `+` Add ILIKE for postgres (case-insensitive LIKE) @@ -23,7 +28,7 @@ - Traits: - Now: `SqliteFromRow`, before: `FromSqliteRow` - Now: `fn sqlite_from_row`, before: `fn from_sqlite_row...` - - derive: + - derive: - Now: `SqliteFromValue`, before: `FromSqliteRow` - Now: `SqliteFromValue`, before: `FromSqliteValue` - Now: `ToSqliteValue`, before: `SqliteToValue` @@ -36,7 +41,7 @@ - `.` update to v0.4.0-rc.5 - `^` sea-query - impl IdenStatic for SIden (and SIden: Clone + Copy) -## 2024-04-18 - `0.4.0-rc.4` +## 2024-04-18 - `0.4.0-rc.4` - SEE: Major refactor/cleanup (see [v0.3.x to v0.4.x document](MIGRATION-v03x-v04x.md) - `+` ToSqliteValue - added ToSqliteValue for simple enum and single tuple struct @@ -47,7 +52,7 @@ - `!` filter - rename context_path to rel - `^` SeaField - add new_concrete -## 2024-03-07 - `0.4.0-rc.2` +## 2024-03-07 - `0.4.0-rc.2` - `!` Major refactor/cleanup (see [v0.3.x to v0.4.x document](MIGRATION-v03x-v04x.md) @@ -62,7 +67,7 @@ - `!` Rename FromSqliteRow::from_rusqlite_row to FromSqliteRow::from_sqlite_row) - `!` Change sqlite::FromRow to FromSqliteRow -- `+` FromSqliteValue for enum +- `+` FromSqliteValue for enum - `+` Add `field::FieldEnum` derive to implement to seaqueryvalue for simple enum (also some code relayout) ## 2024-01-29 - `0.3.8` @@ -72,8 +77,8 @@ ## 2024-01-22 - `0.3.7` - `+` `cast_as` to `filter` -- `!` Potential API break for user using `FieldNode` struct constructor (e.g., `FieldNode {...}`). New property `options: FieldNodeOptions`. Use `options: FieldNodeOptions::default()`. - - Using the `FieldNode::new(...)` functions and every other interface should be unchanged. +- `!` Potential API break for user using `FieldNode` struct constructor (e.g., `FieldNode {...}`). New property `options: FieldNodeOptions`. Use `options: FieldNodeOptions::default()`. + - Using the `FieldNode::new(...)` functions and every other interface should be unchanged. ## 2024-01-20 - `0.3.6` @@ -118,7 +123,7 @@ ## 2023-04-04 - `0.1.0` -- `!` - Major refactoring from `0.0.5`. +- `!` - Major refactoring from `0.0.5`. - `!` - Moved from raw `Vec..` to specialized type `FilterGroups` and `FilterGroup`. -- `!` - Rename all of the `[Type]OpVal` to `OpVal[Type]` with full num type description. -- `+` - Implemented lot of `From` traits. \ No newline at end of file +- `!` - Rename all of the `[Type]OpVal` to `OpVal[Type]` with full num type description. +- `+` - Implemented lot of `From` traits. diff --git a/Cargo.toml b/Cargo.toml index 46698d7..30b35ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,10 +45,10 @@ rusqlite = { workspace = true, optional = true } [workspace.dependencies] sea-query = { version = "0.32", features = ["thread-safe"] } -rusqlite = { version = "0.31" } +rusqlite = { version = "0.32" } [dev-dependencies] serde_with = "3" -rusqlite = {version = "0.31", features = ["bundled"]} -sea-query-rusqlite = {version = "0.6"} +rusqlite = {version = "0.32", features = ["bundled"]} +sea-query-rusqlite = {version = "0.7"} diff --git a/modql-macros/src/derives_filter/mod.rs b/modql-macros/src/derives_filter/mod.rs index 38d4932..ceff084 100644 --- a/modql-macros/src/derives_filter/mod.rs +++ b/modql-macros/src/derives_filter/mod.rs @@ -55,9 +55,16 @@ pub fn derive_filter_nodes_inner(input: TokenStream) -> TokenStream { } else { quote! { None } }; + + let quote_filter_node_options_cast_column_as = if let Some(cast_column_as) = modql_field_attr.cast_column_as { + quote! { Some(#cast_column_as.to_string()) } + } else { + quote! { None } + }; props_filter_node_options.push(quote! { modql::filter::FilterNodeOptions { cast_as: #quote_filter_node_options_cast_as, + cast_column_as: #quote_filter_node_options_cast_column_as, } }); diff --git a/modql-macros/src/derives_filter/utils.rs b/modql-macros/src/derives_filter/utils.rs index 48b2171..7dc410a 100644 --- a/modql-macros/src/derives_filter/utils.rs +++ b/modql-macros/src/derives_filter/utils.rs @@ -8,6 +8,7 @@ pub struct MoqlFilterFieldAttr { pub to_sea_condition_fn: Option, pub to_sea_value_fn: Option, pub cast_as: Option, + pub cast_column_as: Option, } pub fn get_filter_field_attr(field: &Field) -> Result { @@ -17,7 +18,7 @@ pub fn get_filter_field_attr(field: &Field) -> Result = None; let mut to_sea_value_fn: Option = None; let mut cast_as: Option = None; - + let mut cast_column_as: Option = None; if let Some(attribute) = attribute { let nested = attribute.parse_args_with(Punctuated::::parse_terminated)?; @@ -33,6 +34,8 @@ pub fn get_filter_field_attr(field: &Field) -> Result Result, // for db casting. e.g., Will be applied to sea-query value. + pub cast_column_as: Option, // for db casting. e.g., Will be applied to sea-query column. } #[derive(Debug, Clone)] diff --git a/src/filter/ops/op_val_string.rs b/src/filter/ops/op_val_string.rs index c91deb2..74961de 100644 --- a/src/filter/ops/op_val_string.rs +++ b/src/filter/ops/op_val_string.rs @@ -201,7 +201,7 @@ mod json { mod with_sea_query { use super::*; use crate::filter::{sea_is_col_value_null, FilterNodeOptions, SeaResult}; - use crate::into_node_value_expr; + use crate::{into_node_column_expr, into_node_value_expr}; use sea_query::{BinOper, ColumnRef, Condition, ConditionExpression, Expr, Func, SimpleExpr}; #[cfg(feature = "with-ilike")] @@ -215,19 +215,22 @@ mod with_sea_query { ) -> SeaResult { let binary_fn = |op: BinOper, v: String| { let vxpr = into_node_value_expr(v, node_options); - ConditionExpression::SimpleExpr(SimpleExpr::binary(col.clone().into(), op, vxpr)) + let column = into_node_column_expr(col.clone(), node_options); + ConditionExpression::SimpleExpr(SimpleExpr::binary(column.into(), op, vxpr)) }; #[cfg(feature = "with-ilike")] let pg_binary_fn = |op: PgBinOper, v: String| { let vxpr = into_node_value_expr(v, node_options); - ConditionExpression::SimpleExpr(SimpleExpr::binary(col.clone().into(), BinOper::PgOperator(op), vxpr)) + let column = into_node_column_expr(col.clone(), node_options); + ConditionExpression::SimpleExpr(SimpleExpr::binary(column.into(), BinOper::PgOperator(op), vxpr)) }; let binaries_fn = |op: BinOper, v: Vec| { let vxpr_list: Vec = v.into_iter().map(|v| into_node_value_expr(v, node_options)).collect(); let vxpr = SimpleExpr::Tuple(vxpr_list); - ConditionExpression::SimpleExpr(SimpleExpr::binary(col.clone().into(), op, vxpr)) + let column = into_node_column_expr(col.clone(), node_options); + ConditionExpression::SimpleExpr(SimpleExpr::binary(column.into(), op, vxpr)) }; let cond_any_of_fn = |op: BinOper, values: Vec, val_prefix: &str, val_suffix: &str| { diff --git a/src/filter/ops/op_val_value.rs b/src/filter/ops/op_val_value.rs index c0514a0..6fd23bc 100644 --- a/src/filter/ops/op_val_value.rs +++ b/src/filter/ops/op_val_value.rs @@ -89,7 +89,7 @@ mod json { mod with_sea_query { use super::*; use crate::filter::{sea_is_col_value_null, FilterNodeOptions, SeaResult, ToSeaValueFnHolder}; - use crate::into_node_value_expr; + use crate::{into_node_column_expr, into_node_value_expr}; use sea_query::{BinOper, ColumnRef, ConditionExpression, SimpleExpr}; impl OpValValue { @@ -104,11 +104,8 @@ mod with_sea_query { let sea_value = to_sea_value.call(json_value)?; let vxpr = into_node_value_expr(sea_value, node_options); - Ok(ConditionExpression::SimpleExpr(SimpleExpr::binary( - col.clone().into(), - op, - vxpr, - ))) + let column = into_node_column_expr(col.clone(), node_options); + Ok(ConditionExpression::SimpleExpr(SimpleExpr::binary(column.into(), op,vxpr))) }; // -- CondExpr builder for single value @@ -125,11 +122,8 @@ mod with_sea_query { let vxpr = SimpleExpr::Tuple(vxpr_list); // -- Return the condition expression - Ok(ConditionExpression::SimpleExpr(SimpleExpr::binary( - col.clone().into(), - op, - vxpr, - ))) + let column = into_node_column_expr(col.clone(), node_options); + Ok(ConditionExpression::SimpleExpr(SimpleExpr::binary(column.into(), op, vxpr))) }; let cond = match self { diff --git a/src/sea_utils.rs b/src/sea_utils.rs index a789b14..10049a0 100644 --- a/src/sea_utils.rs +++ b/src/sea_utils.rs @@ -1,5 +1,5 @@ use crate::filter::FilterNodeOptions; -use sea_query::{Iden, IdenStatic, SimpleExpr, Value}; +use sea_query::{ColumnRef, Iden, IdenStatic, SimpleExpr, Value}; /// String sea-query `Iden` wrapper #[derive(Debug)] @@ -44,3 +44,12 @@ where } vxpr } + +pub fn into_node_column_expr(col: ColumnRef, node_options: &FilterNodeOptions) -> SimpleExpr { + let Some(cast_column_as) = &node_options.cast_column_as else { + // If no cast is needed, wrap the ColumnRef as a SimpleExpr + return SimpleExpr::Column(col); + }; + + SimpleExpr::Column(col).cast_as(StringIden(cast_column_as.to_string())) +} diff --git a/tests/test_expand_filter_nodes.rs b/tests/test_expand_filter_nodes.rs index 2ec9c2f..50e964e 100644 --- a/tests/test_expand_filter_nodes.rs +++ b/tests/test_expand_filter_nodes.rs @@ -19,6 +19,7 @@ pub struct ProjectFilter { #[modql(rel = "task_tbl")] pub struct TaskFilter { id: Option, + #[modql(cast_column_as = "text")] title: Option, #[modql(rel = "foo_rel")] label: Option, @@ -44,7 +45,7 @@ fn test_expand_filter_nodes_filter_rel() -> Result<()> { // -- Check assert!( - sql.contains(r#"WHERE "task_tbl"."id" = ? AND "task_tbl"."title" = ? AND "foo_rel"."label" = ?"#), + sql.contains(r#"WHERE "task_tbl"."id" = ? AND CAST("task_tbl"."title" AS text) = ? AND "foo_rel"."label" = ?"#), "Incorrect where statment" );