From dd180c38de1d1555cd177d0877b0e0544db85a3b Mon Sep 17 00:00:00 2001 From: spuckhafte Date: Sat, 24 Jan 2026 17:03:48 +0530 Subject: [PATCH 1/5] fix: empty string value in applied filters for get_counts --- src/alerts/alerts_utils.rs | 166 ++++++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 68 deletions(-) diff --git a/src/alerts/alerts_utils.rs b/src/alerts/alerts_utils.rs index 0d5552f31..4695c04f9 100644 --- a/src/alerts/alerts_utils.rs +++ b/src/alerts/alerts_utils.rs @@ -364,81 +364,111 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { &LogicalOperator::And => { let mut exprs = vec![]; for condition in &where_clause.condition_config { - if condition.value.as_ref().is_some_and(|v| !v.is_empty()) { - // ad-hoc error check in case value is some and operator is either `is null` or `is not null` - if condition.operator.eq(&WhereConfigOperator::IsNull) - || condition.operator.eq(&WhereConfigOperator::IsNotNull) - { - return Err("value must be null when operator is either `is null` or `is not null`" - .into()); - } - - let value = condition.value.as_ref().unwrap(); - - let operator_and_value = match condition.operator { - WhereConfigOperator::Contains => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("LIKE '%{escaped_value}%' ESCAPE '\\'") - } - WhereConfigOperator::DoesNotContain => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("NOT LIKE '%{escaped_value}%' ESCAPE '\\'") + match condition.value.as_deref() { + Some(v) if !v.is_empty() => { + // ad-hoc error check in case value is some and operator is either `is null` or `is not null` + if condition.operator.eq(&WhereConfigOperator::IsNull) + || condition.operator.eq(&WhereConfigOperator::IsNotNull) + { + return Err("value must be null when operator is either `is null` or `is not null`" + .into()); } - WhereConfigOperator::ILike => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("ILIKE '%{escaped_value}%' ESCAPE '\\'") - } - WhereConfigOperator::BeginsWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("LIKE '{escaped_value}%' ESCAPE '\\'") + + let value = condition.value.as_ref().unwrap(); + + let operator_and_value = match condition.operator { + WhereConfigOperator::Contains => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("LIKE '%{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::DoesNotContain => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("NOT LIKE '%{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::ILike => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("ILIKE '%{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::BeginsWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("LIKE '{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::DoesNotBeginWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("NOT LIKE '{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::EndsWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("LIKE '%{escaped_value}' ESCAPE '\\'") + } + WhereConfigOperator::DoesNotEndWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("NOT LIKE '%{escaped_value}' ESCAPE '\\'") + } + _ => { + let value = match ValueType::from_string(value.to_owned()) { + ValueType::Number(val) => format!("{val}"), + ValueType::Boolean(val) => format!("{val}"), + ValueType::String(val) => { + format!("'{val}'") + } + }; + format!("{} {}", condition.operator, value) + } + }; + exprs.push(format!("\"{}\" {}", condition.column, operator_and_value)) + } + Some("") => match condition.operator { + WhereConfigOperator::Equal => { + exprs.push(format!("\"{}\" = ''", condition.column)); } - WhereConfigOperator::DoesNotBeginWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("NOT LIKE '{escaped_value}%' ESCAPE '\\'") + WhereConfigOperator::NotEqual => { + exprs.push(format!("\"{}\" != ''", condition.column)); } - WhereConfigOperator::EndsWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("LIKE '%{escaped_value}' ESCAPE '\\'") + _ => { + tracing::warn!( + "ignoring empty string filter for: {} {}", + condition.column, + condition.operator + ); + continue; + // or return an error instead? } - WhereConfigOperator::DoesNotEndWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("NOT LIKE '%{escaped_value}' ESCAPE '\\'") + }, + None => match condition.operator { + WhereConfigOperator::IsNull | WhereConfigOperator::IsNotNull => { + exprs.push(format!("\"{}\" {}", condition.column, condition.operator)) } _ => { - let value = match ValueType::from_string(value.to_owned()) { - ValueType::Number(val) => format!("{val}"), - ValueType::Boolean(val) => format!("{val}"), - ValueType::String(val) => { - format!("'{val}'") - } - }; - format!("{} {}", condition.operator, value) + return Err(format!( + "value cannot be NULL for: {} {}", + condition.column, + condition.operator + )); } - }; - exprs.push(format!("\"{}\" {}", condition.column, operator_and_value)) - } else { - exprs.push(format!("\"{}\" {}", condition.column, condition.operator)) + } + _ => continue, } } From 27f672f4316b8e70d575b298b31730ebae4775d5 Mon Sep 17 00:00:00 2001 From: spuckhafte Date: Sun, 25 Jan 2026 20:36:20 +0530 Subject: [PATCH 2/5] fix: handling boolean and null values in filters --- src/alerts/alert_structs.rs | 36 +++++-- src/alerts/alerts_utils.rs | 205 +++++++++++++++++++----------------- 2 files changed, 140 insertions(+), 101 deletions(-) diff --git a/src/alerts/alert_structs.rs b/src/alerts/alert_structs.rs index c37f30d53..d52f36e01 100644 --- a/src/alerts/alert_structs.rs +++ b/src/alerts/alert_structs.rs @@ -179,7 +179,23 @@ pub struct FilterConfig { pub struct ConditionConfig { pub column: String, pub operator: WhereConfigOperator, - pub value: Option, + pub value: Option, + #[serde(rename = "type")] + pub value_type: Option, +} + +impl ConditionConfig { + pub fn value_as_sql_str(&self) -> Option { + let value = self.value.as_ref()?; + + match value { + serde_json::Value::String(s) => Some(s.clone()), + serde_json::Value::Bool(b) => Some(b.to_string()), + serde_json::Value::Number(n) => Some(n.to_string()), + + _ => None, + } + } } #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] @@ -196,26 +212,32 @@ impl Conditions { LogicalOperator::And | LogicalOperator::Or => { let expr1 = &self.condition_config[0]; let expr2 = &self.condition_config[1]; - let expr1_msg = if expr1.value.as_ref().is_some_and(|v| !v.is_empty()) { + let expr1_msg = if let Some(expr1_val) = expr1.value_as_sql_str() { format!( "{} {} {}", expr1.column, expr1.operator, - expr1.value.as_ref().unwrap() + expr1_val, ) } else { - format!("{} {}", expr1.column, expr1.operator) + match expr1.value { + None => format!("{} {}", expr1.column, expr1.operator), + Some(_) => "unsupported JSON value type in filter".into(), + } }; - let expr2_msg = if expr2.value.as_ref().is_some_and(|v| !v.is_empty()) { + let expr2_msg = if let Some(expr2_val) = expr2.value_as_sql_str() { format!( "{} {} {}", expr2.column, expr2.operator, - expr2.value.as_ref().unwrap() + expr2_val ) } else { - format!("{} {}", expr2.column, expr2.operator) + match expr1.value { + None => format!("{} {}", expr2.column, expr2.operator), + Some(_) => "unspported JSON value type in filter".into(), + } }; format!("[{expr1_msg} {op} {expr2_msg}]") diff --git a/src/alerts/alerts_utils.rs b/src/alerts/alerts_utils.rs index 4695c04f9..9539f2bf1 100644 --- a/src/alerts/alerts_utils.rs +++ b/src/alerts/alerts_utils.rs @@ -28,9 +28,7 @@ use tracing::trace; use crate::{ alerts::{ - AlertTrait, LogicalOperator, WhereConfigOperator, - alert_structs::{AlertQueryResult, Conditions, GroupResult}, - extract_aggregate_aliases, + AlertTrait, LogicalOperator, WhereConfigOperator, alert_structs::{AlertQueryResult, Conditions, GroupResult}, extract_aggregate_aliases }, handlers::http::{ cluster::send_query_request, @@ -364,111 +362,130 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { &LogicalOperator::And => { let mut exprs = vec![]; for condition in &where_clause.condition_config { - match condition.value.as_deref() { - Some(v) if !v.is_empty() => { - // ad-hoc error check in case value is some and operator is either `is null` or `is not null` - if condition.operator.eq(&WhereConfigOperator::IsNull) - || condition.operator.eq(&WhereConfigOperator::IsNotNull) - { - return Err("value must be null when operator is either `is null` or `is not null`" - .into()); - } - - let value = condition.value.as_ref().unwrap(); - - let operator_and_value = match condition.operator { - WhereConfigOperator::Contains => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("LIKE '%{escaped_value}%' ESCAPE '\\'") - } - WhereConfigOperator::DoesNotContain => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("NOT LIKE '%{escaped_value}%' ESCAPE '\\'") - } - WhereConfigOperator::ILike => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("ILIKE '%{escaped_value}%' ESCAPE '\\'") - } - WhereConfigOperator::BeginsWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("LIKE '{escaped_value}%' ESCAPE '\\'") - } - WhereConfigOperator::DoesNotBeginWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("NOT LIKE '{escaped_value}%' ESCAPE '\\'") + match condition.value.clone() { + Some(serde_json::Value::String(value)) => { + if !value.is_empty() { + // ad-hoc error check in case value is some and operator is either `is null` or `is not null` + if condition.operator.eq(&WhereConfigOperator::IsNull) + || condition.operator.eq(&WhereConfigOperator::IsNotNull) + { + return Err("value must be null when operator is either `is null` or `is not null`".into()); } - WhereConfigOperator::EndsWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("LIKE '%{escaped_value}' ESCAPE '\\'") - } - WhereConfigOperator::DoesNotEndWith => { - let escaped_value = value - .replace("'", "\\'") - .replace('%', "\\%") - .replace('_', "\\_"); - format!("NOT LIKE '%{escaped_value}' ESCAPE '\\'") - } - _ => { - let value = match ValueType::from_string(value.to_owned()) { - ValueType::Number(val) => format!("{val}"), - ValueType::Boolean(val) => format!("{val}"), - ValueType::String(val) => { - format!("'{val}'") - } - }; - format!("{} {}", condition.operator, value) + + let operator_and_value = match condition.operator { + WhereConfigOperator::Contains => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("LIKE '%{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::DoesNotContain => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\% + ") + .replace('_', "\\_"); + format!("NOT LIKE '%{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::ILike => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("ILIKE '%{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::BeginsWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("LIKE '{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::DoesNotBeginWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("NOT LIKE '{escaped_value}%' ESCAPE '\\'") + } + WhereConfigOperator::EndsWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("LIKE '%{escaped_value}' ESCAPE '\\'") + } + WhereConfigOperator::DoesNotEndWith => { + let escaped_value = value + .replace("'", "\\'") + .replace('%', "\\%") + .replace('_', "\\_"); + format!("NOT LIKE '%{escaped_value}' ESCAPE '\\'") + } + _ => format!("{} {}", condition.operator, value) + }; + exprs.push(format!("\"{}\" {}", condition.column, operator_and_value)); + } else { + match condition.operator { + WhereConfigOperator::Equal => { + exprs.push(format!("\"{}\" = ''", condition.column)); + } + WhereConfigOperator::NotEqual => { + exprs.push(format!("\"{}\" != ''", condition.column)); + } + _ => { + tracing::warn!( + "ignoring empty string filter for: {} {}", + condition.column, + condition.operator + ); + continue; + // or return an error instead? + } } + } + } + + Some(_) => { + let Some(value) = condition.value_as_sql_str() else { + return Err("unsupported JSON value type in filters".into()); }; - exprs.push(format!("\"{}\" {}", condition.column, operator_and_value)) + exprs.push(format!( + "\"{}\" {} {}", + condition.column, + condition.operator, + value + )) } - Some("") => match condition.operator { - WhereConfigOperator::Equal => { - exprs.push(format!("\"{}\" = ''", condition.column)); - } - WhereConfigOperator::NotEqual => { - exprs.push(format!("\"{}\" != ''", condition.column)); - } - _ => { - tracing::warn!( - "ignoring empty string filter for: {} {}", - condition.column, - condition.operator - ); - continue; - // or return an error instead? - } - }, + None => match condition.operator { WhereConfigOperator::IsNull | WhereConfigOperator::IsNotNull => { exprs.push(format!("\"{}\" {}", condition.column, condition.operator)) } _ => { - return Err(format!( + if condition.value_type.as_ref().is_some_and(|v| v == "null") { + match condition.operator { + WhereConfigOperator::Equal => { + exprs.push(format!("\"{}\" {}", condition.column, WhereConfigOperator::IsNull)) + } + + WhereConfigOperator::NotEqual => { + exprs.push(format!("\"{}\" {}", condition.column, WhereConfigOperator::IsNotNull)) + } + _ => { + return Err(format!("invalid operator [{}] with [null]", condition.operator)); + } + } + } else { + return Err(format!( "value cannot be NULL for: {} {}", condition.column, condition.operator - )); + )); + } } } - _ => continue, } } From 32d590b53a2e3e6d00f871aff670a3b51d82a07d Mon Sep 17 00:00:00 2001 From: spuckhafte Date: Sun, 25 Jan 2026 21:33:55 +0530 Subject: [PATCH 3/5] chore: fmt, clippy --- src/alerts/alert_structs.rs | 26 +++++--------- src/alerts/alerts_utils.rs | 69 ++++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/alerts/alert_structs.rs b/src/alerts/alert_structs.rs index d52f36e01..fbfd363b7 100644 --- a/src/alerts/alert_structs.rs +++ b/src/alerts/alert_structs.rs @@ -187,7 +187,7 @@ pub struct ConditionConfig { impl ConditionConfig { pub fn value_as_sql_str(&self) -> Option { let value = self.value.as_ref()?; - + match value { serde_json::Value::String(s) => Some(s.clone()), serde_json::Value::Bool(b) => Some(b.to_string()), @@ -212,31 +212,21 @@ impl Conditions { LogicalOperator::And | LogicalOperator::Or => { let expr1 = &self.condition_config[0]; let expr2 = &self.condition_config[1]; - let expr1_msg = if let Some(expr1_val) = expr1.value_as_sql_str() { - format!( - "{} {} {}", - expr1.column, - expr1.operator, - expr1_val, - ) + let expr1_msg = if let Some(expr1_val) = expr1.value_as_sql_str() { + format!("{} {} {}", expr1.column, expr1.operator, expr1_val,) } else { match expr1.value { - None => format!("{} {}", expr1.column, expr1.operator), - Some(_) => "unsupported JSON value type in filter".into(), + None => format!("{} {}", expr1.column, expr1.operator), + Some(_) => "unsupported JSON value type in filter".into(), } }; let expr2_msg = if let Some(expr2_val) = expr2.value_as_sql_str() { - format!( - "{} {} {}", - expr2.column, - expr2.operator, - expr2_val - ) + format!("{} {} {}", expr2.column, expr2.operator, expr2_val) } else { - match expr1.value { + match expr2.value { None => format!("{} {}", expr2.column, expr2.operator), - Some(_) => "unspported JSON value type in filter".into(), + Some(_) => "unsupported JSON value type in filter".into(), } }; diff --git a/src/alerts/alerts_utils.rs b/src/alerts/alerts_utils.rs index 9539f2bf1..59c13284c 100644 --- a/src/alerts/alerts_utils.rs +++ b/src/alerts/alerts_utils.rs @@ -28,7 +28,9 @@ use tracing::trace; use crate::{ alerts::{ - AlertTrait, LogicalOperator, WhereConfigOperator, alert_structs::{AlertQueryResult, Conditions, GroupResult}, extract_aggregate_aliases + AlertTrait, LogicalOperator, WhereConfigOperator, + alert_structs::{AlertQueryResult, Conditions, GroupResult}, + extract_aggregate_aliases, }, handlers::http::{ cluster::send_query_request, @@ -363,7 +365,7 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { let mut exprs = vec![]; for condition in &where_clause.condition_config { match condition.value.clone() { - Some(serde_json::Value::String(value)) => { + Some(serde_json::Value::String(value)) => { if !value.is_empty() { // ad-hoc error check in case value is some and operator is either `is null` or `is not null` if condition.operator.eq(&WhereConfigOperator::IsNull) @@ -371,7 +373,7 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { { return Err("value must be null when operator is either `is null` or `is not null`".into()); } - + let operator_and_value = match condition.operator { WhereConfigOperator::Contains => { let escaped_value = value @@ -383,8 +385,11 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { WhereConfigOperator::DoesNotContain => { let escaped_value = value .replace("'", "\\'") - .replace('%', "\\% - ") + .replace( + '%', + "\\% + ", + ) .replace('_', "\\_"); format!("NOT LIKE '%{escaped_value}%' ESCAPE '\\'") } @@ -423,9 +428,16 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { .replace('_', "\\_"); format!("NOT LIKE '%{escaped_value}' ESCAPE '\\'") } - _ => format!("{} {}", condition.operator, value) + _ => format!( + "{} '{}'", + condition.operator, + value.replace("'", "''") + ), }; - exprs.push(format!("\"{}\" {}", condition.column, operator_and_value)); + exprs.push(format!( + "\"{}\" {}", + condition.column, operator_and_value + )); } else { match condition.operator { WhereConfigOperator::Equal => { @@ -446,46 +458,49 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { } } } - + Some(_) => { let Some(value) = condition.value_as_sql_str() else { return Err("unsupported JSON value type in filters".into()); }; exprs.push(format!( "\"{}\" {} {}", - condition.column, - condition.operator, - value + condition.column, condition.operator, value )) } - + None => match condition.operator { - WhereConfigOperator::IsNull | WhereConfigOperator::IsNotNull => { - exprs.push(format!("\"{}\" {}", condition.column, condition.operator)) - } + WhereConfigOperator::IsNull | WhereConfigOperator::IsNotNull => exprs + .push(format!("\"{}\" {}", condition.column, condition.operator)), _ => { if condition.value_type.as_ref().is_some_and(|v| v == "null") { match condition.operator { - WhereConfigOperator::Equal => { - exprs.push(format!("\"{}\" {}", condition.column, WhereConfigOperator::IsNull)) - } - - WhereConfigOperator::NotEqual => { - exprs.push(format!("\"{}\" {}", condition.column, WhereConfigOperator::IsNotNull)) - } + WhereConfigOperator::Equal => exprs.push(format!( + "\"{}\" {}", + condition.column, + WhereConfigOperator::IsNull + )), + + WhereConfigOperator::NotEqual => exprs.push(format!( + "\"{}\" {}", + condition.column, + WhereConfigOperator::IsNotNull + )), _ => { - return Err(format!("invalid operator [{}] with [null]", condition.operator)); + return Err(format!( + "invalid operator [{}] with [null]", + condition.operator + )); } } } else { return Err(format!( - "value cannot be NULL for: {} {}", - condition.column, - condition.operator + "value cannot be NULL for: {} {}", + condition.column, condition.operator )); } } - } + }, } } From 6e1e2ea60b0943edac2409d7172ea5d924399c2d Mon Sep 17 00:00:00 2001 From: spuckhafte Date: Mon, 26 Jan 2026 23:20:15 +0530 Subject: [PATCH 4/5] chore: restructure --- src/alerts/alerts_utils.rs | 39 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/alerts/alerts_utils.rs b/src/alerts/alerts_utils.rs index 59c13284c..eb507e4f6 100644 --- a/src/alerts/alerts_utils.rs +++ b/src/alerts/alerts_utils.rs @@ -385,11 +385,7 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { WhereConfigOperator::DoesNotContain => { let escaped_value = value .replace("'", "\\'") - .replace( - '%', - "\\% - ", - ) + .replace('%', "\\%") .replace('_', "\\_"); format!("NOT LIKE '%{escaped_value}%' ESCAPE '\\'") } @@ -472,27 +468,14 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { None => match condition.operator { WhereConfigOperator::IsNull | WhereConfigOperator::IsNotNull => exprs .push(format!("\"{}\" {}", condition.column, condition.operator)), - _ => { + + WhereConfigOperator::Equal | WhereConfigOperator::NotEqual => { if condition.value_type.as_ref().is_some_and(|v| v == "null") { - match condition.operator { - WhereConfigOperator::Equal => exprs.push(format!( - "\"{}\" {}", - condition.column, - WhereConfigOperator::IsNull - )), - - WhereConfigOperator::NotEqual => exprs.push(format!( - "\"{}\" {}", - condition.column, - WhereConfigOperator::IsNotNull - )), - _ => { - return Err(format!( - "invalid operator [{}] with [null]", - condition.operator - )); - } - } + let operator = match condition.operator { + WhereConfigOperator::Equal => WhereConfigOperator::IsNull, + _ => WhereConfigOperator::IsNotNull, + }; + exprs.push(format!("\"{}\" {}", condition.column, operator)); } else { return Err(format!( "value cannot be NULL for: {} {}", @@ -500,6 +483,12 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { )); } } + _ => { + return Err(format!( + "invalid null operation: [{}]", + condition.operator + )); + } }, } } From 76de1d8070c55c45753e3c84e7b33a502bcd4c7f Mon Sep 17 00:00:00 2001 From: spuckhafte Date: Mon, 26 Jan 2026 23:41:50 +0530 Subject: [PATCH 5/5] chore: rephrase error msg --- src/alerts/alerts_utils.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/alerts/alerts_utils.rs b/src/alerts/alerts_utils.rs index eb507e4f6..b0261ddf3 100644 --- a/src/alerts/alerts_utils.rs +++ b/src/alerts/alerts_utils.rs @@ -478,8 +478,7 @@ pub fn get_filter_string(where_clause: &Conditions) -> Result { exprs.push(format!("\"{}\" {}", condition.column, operator)); } else { return Err(format!( - "value cannot be NULL for: {} {}", - condition.column, condition.operator + "For [value: null], explicitly set [type: \"null\"]" )); } }