use serde::Deserialize;
use std::collections::HashSet;
#[derive(Debug, Deserialize, Clone, Default)]
pub struct FiltersFeature {
pub enable: bool,
#[serde(flatten)]
pub meta: MetaFilters,
#[serde(default)]
pub indicators: IndicatorFilters,
#[serde(default)]
pub sql: SqlFilters,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct MetaFilters {
pub start_ts: Option<String>,
pub end_ts: Option<String>,
pub sess_ids: Option<Vec<String>>,
pub thrd_ids: Option<Vec<String>>,
pub usernames: Option<Vec<String>>,
pub trxids: Option<Vec<String>>,
pub statements: Option<Vec<String>>,
pub appnames: Option<Vec<String>>,
pub client_ips: Option<Vec<String>>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct IndicatorFilters {
pub exec_ids: Option<Vec<i64>>,
pub min_runtime_ms: Option<i64>,
pub min_row_count: Option<i64>,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct SqlFilters {
pub include_patterns: Option<Vec<String>>,
pub exclude_patterns: Option<Vec<String>>,
}
impl FiltersFeature {
pub fn validate() {}
#[must_use]
pub fn has_filters(&self) -> bool {
self.meta.start_ts.is_some()
|| self.meta.end_ts.is_some()
|| self.meta.has_filters()
|| self.indicators.has_filters()
|| self.sql.has_filters()
}
#[must_use]
pub fn has_transaction_filters(&self) -> bool {
if !self.enable && !self.has_filters() {
return false;
}
self.indicators.has_filters() || self.sql.has_filters()
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn should_keep(
&self,
ts: &str,
trxid: &str,
ip: &str,
sess: &str,
thrd: &str,
user: &str,
stmt: &str,
app: &str,
tag: Option<&str>,
) -> bool {
if !self.has_filters() {
return true;
}
if !self.enable && !self.has_filters() {
return true;
}
if let Some(start) = &self.meta.start_ts {
if ts < start.as_str() && !ts.starts_with(start.as_str()) {
return false;
}
}
if let Some(end) = &self.meta.end_ts {
if ts > end.as_str() && !ts.starts_with(end.as_str()) {
return false;
}
}
if !self.meta.has_filters() {
return true;
}
self.meta
.should_keep(ts, trxid, ip, sess, thrd, user, stmt, app, tag)
}
pub fn merge_found_trxids(&mut self, trxids: Vec<String>) {
if (!self.enable && !self.has_filters()) || trxids.is_empty() {
return;
}
let current = self.meta.trxids.take().unwrap_or_default();
let mut set: HashSet<_> = current.into_iter().collect();
for id in trxids {
set.insert(id);
}
self.meta.trxids = Some(set.into_iter().collect());
}
}
impl MetaFilters {
#[must_use]
pub fn has_filters(&self) -> bool {
self.trxids.as_ref().is_some_and(|v| !v.is_empty())
|| self.client_ips.as_ref().is_some_and(|v| !v.is_empty())
|| self.sess_ids.as_ref().is_some_and(|v| !v.is_empty())
|| self.thrd_ids.as_ref().is_some_and(|v| !v.is_empty())
|| self.usernames.as_ref().is_some_and(|v| !v.is_empty())
|| self.statements.as_ref().is_some_and(|v| !v.is_empty())
|| self.appnames.as_ref().is_some_and(|v| !v.is_empty())
|| self.tags.as_ref().is_some_and(|v| !v.is_empty())
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn should_keep(
&self,
_ts: &str,
trxid: &str,
ip: &str,
sess: &str,
thrd: &str,
user: &str,
stmt: &str,
app: &str,
tag: Option<&str>,
) -> bool {
Self::match_list(self.trxids.as_ref(), trxid, true)
|| Self::match_list(self.client_ips.as_ref(), ip, false)
|| Self::match_list(self.sess_ids.as_ref(), sess, false)
|| Self::match_list(self.thrd_ids.as_ref(), thrd, false)
|| Self::match_list(self.usernames.as_ref(), user, false)
|| Self::match_list(self.statements.as_ref(), stmt, false)
|| Self::match_list(self.appnames.as_ref(), app, false)
|| tag.is_some_and(|t| Self::match_list(self.tags.as_ref(), t, false))
}
fn match_list(list: Option<&Vec<String>>, val: &str, exact: bool) -> bool {
if let Some(items) = list {
if items.is_empty() {
return false;
}
if exact {
items.iter().any(|i| i == val)
} else {
items.iter().any(|i| val.contains(i))
}
} else {
false
}
}
}
impl IndicatorFilters {
#[must_use]
pub fn has_filters(&self) -> bool {
self.exec_ids.as_ref().is_some_and(|v| !v.is_empty())
|| self.min_runtime_ms.is_some()
|| self.min_row_count.is_some()
}
#[must_use]
pub fn matches(&self, exec_id: i64, runtime_ms: i64, row_count: i64) -> bool {
if !self.has_filters() {
return false;
}
if let Some(ids) = &self.exec_ids {
if ids.contains(&exec_id) {
return true;
}
}
if let Some(min_t) = self.min_runtime_ms {
if runtime_ms >= min_t {
return true;
}
}
if let Some(min_r) = self.min_row_count {
if row_count >= min_r {
return true;
}
}
false
}
}
impl SqlFilters {
#[must_use]
pub fn has_filters(&self) -> bool {
self.include_patterns
.as_ref()
.is_some_and(|v| !v.is_empty())
|| self
.exclude_patterns
.as_ref()
.is_some_and(|v| !v.is_empty())
}
#[must_use]
pub fn matches(&self, sql: &str) -> bool {
if !self.has_filters() {
return false;
}
let include_match = if let Some(patterns) = &self.include_patterns {
if patterns.is_empty() {
true
} else {
patterns.iter().any(|p| sql.contains(p))
}
} else {
true
};
if !include_match {
return false;
}
if let Some(patterns) = &self.exclude_patterns {
if patterns.iter().any(|p| sql.contains(p)) {
return false;
}
}
true
}
}