Skip to content

Commit 329b787

Browse files
authored
Merge branch 'main' into targets-separation
Signed-off-by: parmesant <[email protected]>
2 parents 484a0c7 + 32d105e commit 329b787

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1818
-333
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "parseable"
3-
version = "2.3.3"
3+
version = "2.3.5"
44
authors = ["Parseable Team <[email protected]>"]
55
edition = "2021"
66
rust-version = "1.83.0"
@@ -139,8 +139,8 @@ arrow = "54.0.0"
139139
temp-dir = "0.1.14"
140140

141141
[package.metadata.parseable_ui]
142-
assets-url = "https://parseable-prism-build.s3.us-east-2.amazonaws.com/v2.3.2/build.zip"
143-
assets-sha1 = "35cfa3ab692ab0debf6666e5e2e1876fa7de4a02"
142+
assets-url = "https://parseable-prism-build.s3.us-east-2.amazonaws.com/v2.3.5/build.zip"
143+
assets-sha1 = "3e703ef8bedf8ae55fd31713f6267ad14ad3d29d"
144144

145145
[features]
146146
debug = []

build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ mod ui {
151151
.expect("has segemnts")
152152
.find(|v| v.starts_with('v'))
153153
.expect("version segement");
154-
println!("cargo:rustc-env=UI_VERSION={}", ui_version);
154+
println!("cargo:rustc-env=UI_VERSION={ui_version}");
155155
}
156156

157157
Ok(())

src/about.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@ pub fn get_latest_release() -> &'static Option<LatestRelease> {
7171
}
7272

7373
// User Agent for Download API call
74-
// Format: Parseable/<UID>/<version>/<commit_hash> (<OS>; <Platform>)
75-
pub fn user_agent(uid: &Ulid) -> String {
74+
// Format: Parseable/<UID>/<version>/<commit_hash>/<send_analytics> (<OS>; <Platform>)
75+
pub fn user_agent(uid: &Ulid, send_analytics: bool) -> String {
7676
analytics::refresh_sys_info();
7777
format!(
78-
"Parseable/{}/{}/{} ({:?}; {})",
78+
"Parseable/{}/{}/{}/{} ({:?}; {})",
7979
uid,
8080
current().released_version,
8181
current().commit_hash,
82+
send_analytics,
8283
System::name().unwrap_or_default(),
8384
platform()
8485
)

src/alerts/alerts_utils.rs

Lines changed: 151 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*
1717
*/
1818

19+
use std::fmt::Display;
20+
1921
use arrow_array::{Float64Array, Int64Array, RecordBatch};
2022
use datafusion::{
2123
functions_aggregate::{
@@ -86,7 +88,7 @@ async fn execute_base_query(
8688
original_query: &str,
8789
) -> Result<DataFrame, AlertError> {
8890
let stream_name = query.first_table_name().ok_or_else(|| {
89-
AlertError::CustomError(format!("Table name not found in query- {}", original_query))
91+
AlertError::CustomError(format!("Table name not found in query- {original_query}"))
9092
})?;
9193

9294
let time_partition = PARSEABLE.get_stream(&stream_name)?.get_time_partition();
@@ -339,46 +341,149 @@ fn get_filter_expr(where_clause: &Conditions) -> Expr {
339341
}
340342
}
341343

344+
pub fn get_filter_string(where_clause: &Conditions) -> Result<String, String> {
345+
match &where_clause.operator {
346+
Some(op) => match op {
347+
LogicalOperator::And => {
348+
let mut exprs = vec![];
349+
for condition in &where_clause.condition_config {
350+
if condition.value.as_ref().is_some_and(|v| !v.is_empty()) {
351+
// ad-hoc error check in case value is some and operator is either `is null` or `is not null`
352+
if condition.operator.eq(&WhereConfigOperator::IsNull)
353+
|| condition.operator.eq(&WhereConfigOperator::IsNotNull)
354+
{
355+
return Err("value must be null when operator is either `is null` or `is not null`"
356+
.into());
357+
}
358+
359+
let value = condition.value.as_ref().unwrap();
360+
361+
let operator_and_value = match condition.operator {
362+
WhereConfigOperator::Contains => {
363+
let escaped_value = value
364+
.replace("'", "\\'")
365+
.replace('%', "\\%")
366+
.replace('_', "\\_");
367+
format!("LIKE '%{escaped_value}%' ESCAPE '\\'")
368+
}
369+
WhereConfigOperator::DoesNotContain => {
370+
let escaped_value = value
371+
.replace("'", "\\'")
372+
.replace('%', "\\%")
373+
.replace('_', "\\_");
374+
format!("NOT LIKE '%{escaped_value}%' ESCAPE '\\'")
375+
}
376+
WhereConfigOperator::ILike => {
377+
let escaped_value = value
378+
.replace("'", "\\'")
379+
.replace('%', "\\%")
380+
.replace('_', "\\_");
381+
format!("ILIKE '%{escaped_value}%' ESCAPE '\\'")
382+
}
383+
WhereConfigOperator::BeginsWith => {
384+
let escaped_value = value
385+
.replace("'", "\\'")
386+
.replace('%', "\\%")
387+
.replace('_', "\\_");
388+
format!("LIKE '{escaped_value}%' ESCAPE '\\'")
389+
}
390+
WhereConfigOperator::DoesNotBeginWith => {
391+
let escaped_value = value
392+
.replace("'", "\\'")
393+
.replace('%', "\\%")
394+
.replace('_', "\\_");
395+
format!("NOT LIKE '{escaped_value}%' ESCAPE '\\'")
396+
}
397+
WhereConfigOperator::EndsWith => {
398+
let escaped_value = value
399+
.replace("'", "\\'")
400+
.replace('%', "\\%")
401+
.replace('_', "\\_");
402+
format!("LIKE '%{escaped_value}' ESCAPE '\\'")
403+
}
404+
WhereConfigOperator::DoesNotEndWith => {
405+
let escaped_value = value
406+
.replace("'", "\\'")
407+
.replace('%', "\\%")
408+
.replace('_', "\\_");
409+
format!("NOT LIKE '%{escaped_value}' ESCAPE '\\'")
410+
}
411+
_ => {
412+
let value = match NumberOrString::from_string(value.to_owned()) {
413+
NumberOrString::Number(val) => format!("{val}"),
414+
NumberOrString::String(val) => {
415+
format!("'{val}'")
416+
}
417+
};
418+
format!("{} {}", condition.operator, value)
419+
}
420+
};
421+
exprs.push(format!("\"{}\" {}", condition.column, operator_and_value))
422+
} else {
423+
exprs.push(format!("\"{}\" {}", condition.column, condition.operator))
424+
}
425+
}
426+
427+
Ok(exprs.join(" AND "))
428+
}
429+
_ => Err(String::from("Invalid option 'or', only 'and' is supported")),
430+
},
431+
_ => Err(String::from(
432+
"Invalid option 'null', only 'and' is supported",
433+
)),
434+
}
435+
}
436+
342437
fn match_alert_operator(expr: &ConditionConfig) -> Expr {
343438
// the form accepts value as a string
344439
// if it can be parsed as a number, then parse it
345440
// else keep it as a string
346-
let value = NumberOrString::from_string(expr.value.clone());
347-
348-
// for maintaining column case
349-
let column = format!(r#""{}""#, expr.column);
350-
match expr.operator {
351-
WhereConfigOperator::Equal => col(column).eq(lit(value)),
352-
WhereConfigOperator::NotEqual => col(column).not_eq(lit(value)),
353-
WhereConfigOperator::LessThan => col(column).lt(lit(value)),
354-
WhereConfigOperator::GreaterThan => col(column).gt(lit(value)),
355-
WhereConfigOperator::LessThanOrEqual => col(column).lt_eq(lit(value)),
356-
WhereConfigOperator::GreaterThanOrEqual => col(column).gt_eq(lit(value)),
357-
WhereConfigOperator::IsNull => col(column).is_null(),
358-
WhereConfigOperator::IsNotNull => col(column).is_not_null(),
359-
WhereConfigOperator::ILike => col(column).ilike(lit(&expr.value)),
360-
WhereConfigOperator::Contains => col(column).like(lit(&expr.value)),
361-
WhereConfigOperator::BeginsWith => Expr::BinaryExpr(BinaryExpr::new(
362-
Box::new(col(column)),
363-
Operator::RegexIMatch,
364-
Box::new(lit(format!("^{}", expr.value))),
365-
)),
366-
WhereConfigOperator::EndsWith => Expr::BinaryExpr(BinaryExpr::new(
367-
Box::new(col(column)),
368-
Operator::RegexIMatch,
369-
Box::new(lit(format!("{}$", expr.value))),
370-
)),
371-
WhereConfigOperator::DoesNotContain => col(column).not_ilike(lit(&expr.value)),
372-
WhereConfigOperator::DoesNotBeginWith => Expr::BinaryExpr(BinaryExpr::new(
373-
Box::new(col(column)),
374-
Operator::RegexNotIMatch,
375-
Box::new(lit(format!("^{}", expr.value))),
376-
)),
377-
WhereConfigOperator::DoesNotEndWith => Expr::BinaryExpr(BinaryExpr::new(
378-
Box::new(col(column)),
379-
Operator::RegexNotIMatch,
380-
Box::new(lit(format!("{}$", expr.value))),
381-
)),
441+
if expr.value.as_ref().is_some_and(|v| !v.is_empty()) {
442+
let value = expr.value.as_ref().unwrap();
443+
let value = NumberOrString::from_string(value.clone());
444+
445+
// for maintaining column case
446+
let column = format!(r#""{}""#, expr.column);
447+
match expr.operator {
448+
WhereConfigOperator::Equal => col(column).eq(lit(value)),
449+
WhereConfigOperator::NotEqual => col(column).not_eq(lit(value)),
450+
WhereConfigOperator::LessThan => col(column).lt(lit(value)),
451+
WhereConfigOperator::GreaterThan => col(column).gt(lit(value)),
452+
WhereConfigOperator::LessThanOrEqual => col(column).lt_eq(lit(value)),
453+
WhereConfigOperator::GreaterThanOrEqual => col(column).gt_eq(lit(value)),
454+
WhereConfigOperator::ILike => col(column).ilike(lit(value)),
455+
WhereConfigOperator::Contains => col(column).like(lit(value)),
456+
WhereConfigOperator::BeginsWith => Expr::BinaryExpr(BinaryExpr::new(
457+
Box::new(col(column)),
458+
Operator::RegexIMatch,
459+
Box::new(lit(format!("^{value}"))),
460+
)),
461+
WhereConfigOperator::EndsWith => Expr::BinaryExpr(BinaryExpr::new(
462+
Box::new(col(column)),
463+
Operator::RegexIMatch,
464+
Box::new(lit(format!("{value}$"))),
465+
)),
466+
WhereConfigOperator::DoesNotContain => col(column).not_ilike(lit(value)),
467+
WhereConfigOperator::DoesNotBeginWith => Expr::BinaryExpr(BinaryExpr::new(
468+
Box::new(col(column)),
469+
Operator::RegexNotIMatch,
470+
Box::new(lit(format!("^{value}"))),
471+
)),
472+
WhereConfigOperator::DoesNotEndWith => Expr::BinaryExpr(BinaryExpr::new(
473+
Box::new(col(column)),
474+
Operator::RegexNotIMatch,
475+
Box::new(lit(format!("{value}$"))),
476+
)),
477+
_ => unreachable!("value must not be null for operators other than `is null` and `is not null`. Should've been caught in validation")
478+
}
479+
} else {
480+
// for maintaining column case
481+
let column = format!(r#""{}""#, expr.column);
482+
match expr.operator {
483+
WhereConfigOperator::IsNull => col(column).is_null(),
484+
WhereConfigOperator::IsNotNull => col(column).is_not_null(),
485+
_ => unreachable!("value must be null for `is null` and `is not null`. Should've been caught in validation")
486+
}
382487
}
383488
}
384489

@@ -417,3 +522,12 @@ impl NumberOrString {
417522
}
418523
}
419524
}
525+
526+
impl Display for NumberOrString {
527+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528+
match self {
529+
NumberOrString::Number(v) => write!(f, "{v}"),
530+
NumberOrString::String(v) => write!(f, "{v}"),
531+
}
532+
}
533+
}

src/alerts/mod.rs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ impl Display for AlertOperator {
219219
}
220220
}
221221

222-
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, FromStr)]
222+
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, FromStr, PartialEq, Eq)]
223223
#[serde(rename_all = "camelCase")]
224224
pub enum WhereConfigOperator {
225225
#[serde(rename = "=")]
@@ -325,7 +325,7 @@ pub struct FilterConfig {
325325
pub struct ConditionConfig {
326326
pub column: String,
327327
pub operator: WhereConfigOperator,
328-
pub value: String,
328+
pub value: Option<String>,
329329
}
330330

331331
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
@@ -342,20 +342,38 @@ impl Conditions {
342342
LogicalOperator::And | LogicalOperator::Or => {
343343
let expr1 = &self.condition_config[0];
344344
let expr2 = &self.condition_config[1];
345-
format!(
346-
"[{} {} {} {op} {} {} {}]",
347-
expr1.column,
348-
expr1.operator,
349-
expr1.value,
350-
expr2.column,
351-
expr2.operator,
352-
expr2.value
353-
)
345+
let expr1_msg = if expr1.value.as_ref().is_some_and(|v| !v.is_empty()) {
346+
format!(
347+
"{} {} {}",
348+
expr1.column,
349+
expr1.operator,
350+
expr1.value.as_ref().unwrap()
351+
)
352+
} else {
353+
format!("{} {}", expr1.column, expr1.operator)
354+
};
355+
356+
let expr2_msg = if expr2.value.as_ref().is_some_and(|v| !v.is_empty()) {
357+
format!(
358+
"{} {} {}",
359+
expr2.column,
360+
expr2.operator,
361+
expr2.value.as_ref().unwrap()
362+
)
363+
} else {
364+
format!("{} {}", expr2.column, expr2.operator)
365+
};
366+
367+
format!("[{expr1_msg} {op} {expr2_msg}]")
354368
}
355369
},
356370
None => {
357371
let expr = &self.condition_config[0];
358-
format!("[{} {} {}]", expr.column, expr.operator, expr.value)
372+
if let Some(val) = &expr.value {
373+
format!("{} {} {}", expr.column, expr.operator, val)
374+
} else {
375+
format!("{} {}", expr.column, expr.operator)
376+
}
359377
}
360378
}
361379
}
@@ -655,6 +673,27 @@ impl AlertConfig {
655673
}
656674
}
657675
}
676+
677+
// validate that the value should be None in case of `is null` and `is not null`
678+
for condition in config.condition_config.iter() {
679+
let needs_no_value = matches!(
680+
condition.operator,
681+
WhereConfigOperator::IsNull | WhereConfigOperator::IsNotNull
682+
);
683+
684+
if needs_no_value && condition.value.as_ref().is_some_and(|v| !v.is_empty()) {
685+
return Err(AlertError::CustomError(
686+
"value must be null when operator is either `is null` or `is not null`"
687+
.into(),
688+
));
689+
}
690+
if !needs_no_value && condition.value.as_ref().is_none_or(|v| v.is_empty()) {
691+
return Err(AlertError::CustomError(
692+
"value must not be null when operator is neither `is null` nor `is not null`"
693+
.into(),
694+
));
695+
}
696+
}
658697
Ok(())
659698
}
660699

src/catalog/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,8 @@ pub fn partition_path(
459459
let lower = lower_bound.date_naive().format("%Y-%m-%d").to_string();
460460
let upper = upper_bound.date_naive().format("%Y-%m-%d").to_string();
461461
if lower == upper {
462-
RelativePathBuf::from_iter([stream, &format!("date={}", lower)])
462+
RelativePathBuf::from_iter([stream, &format!("date={lower}")])
463463
} else {
464-
RelativePathBuf::from_iter([stream, &format!("date={}:{}", lower, upper)])
464+
RelativePathBuf::from_iter([stream, &format!("date={lower}:{upper}")])
465465
}
466466
}

0 commit comments

Comments
 (0)