feat: allow to format delete predicates as SQL strings

This is required to feed them back into the gRPC delete API.
pull/24376/head
Marco Neumann 2021-11-23 10:14:39 +01:00
parent 50e9e02ff7
commit a9ec0720b2
2 changed files with 239 additions and 7 deletions

View File

@ -1,4 +1,5 @@
use crate::timestamp::TimestampRange;
use std::{fmt::Write, num::FpCategory};
/// Represents a parsed delete predicate for evaluation by the InfluxDB IOx
/// query engine.
@ -16,6 +17,20 @@ pub struct DeletePredicate {
pub exprs: Vec<DeleteExpr>,
}
impl DeletePredicate {
/// Format expr to SQL string.
pub fn expr_sql_string(&self) -> String {
let mut out = String::new();
for expr in &self.exprs {
if !out.is_empty() {
write!(&mut out, " AND ").expect("writing to a string shouldn't fail");
}
write!(&mut out, "{}", expr).expect("writing to a string shouldn't fail");
}
out
}
}
/// Single expression to be used as parts of a predicate.
///
/// Only very simple expression of the type `<column> <op> <scalar>` are supported.
@ -55,7 +70,15 @@ impl DeleteExpr {
impl std::fmt::Display for DeleteExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}{}", self.column(), self.op(), self.scalar())
write!(
f,
r#""{}"{}{}"#,
self.column()
.replace(r#"\"#, r#"\\"#)
.replace(r#"""#, r#"\""#),
self.op(),
self.scalar(),
)
}
}
@ -93,8 +116,217 @@ impl std::fmt::Display for Scalar {
match self {
Scalar::Bool(value) => value.fmt(f),
Scalar::I64(value) => value.fmt(f),
Scalar::F64(value) => value.fmt(f),
Scalar::String(value) => write!(f, "'{}'", value),
Scalar::F64(value) => match value.classify() {
FpCategory::Nan => write!(f, "'NaN'"),
FpCategory::Infinite if *value.as_ref() < 0.0 => write!(f, "'-Infinity'"),
FpCategory::Infinite => write!(f, "'Infinity'"),
_ => write!(f, "{:?}", value.as_ref()),
},
Scalar::String(value) => {
write!(
f,
"'{}'",
value.replace(r#"\"#, r#"\\"#).replace(r#"'"#, r#"\'"#),
)
}
}
}
}
#[cfg(test)]
mod tests {
use ordered_float::OrderedFloat;
use super::*;
#[test]
fn test_expr_to_sql_no_expressions() {
let pred = DeletePredicate {
range: TimestampRange { start: 1, end: 2 },
exprs: vec![],
};
assert_eq!(&pred.expr_sql_string(), "");
}
#[test]
fn test_expr_to_sql_operators() {
let pred = DeletePredicate {
range: TimestampRange { start: 1, end: 2 },
exprs: vec![
DeleteExpr {
column: String::from("col1"),
op: Op::Eq,
scalar: Scalar::I64(1),
},
DeleteExpr {
column: String::from("col2"),
op: Op::Ne,
scalar: Scalar::I64(2),
},
],
};
assert_eq!(&pred.expr_sql_string(), r#""col1"=1 AND "col2"!=2"#);
}
#[test]
fn test_expr_to_sql_column_escape() {
let pred = DeletePredicate {
range: TimestampRange { start: 1, end: 2 },
exprs: vec![
DeleteExpr {
column: String::from("col 1"),
op: Op::Eq,
scalar: Scalar::I64(1),
},
DeleteExpr {
column: String::from(r#"col\2"#),
op: Op::Eq,
scalar: Scalar::I64(2),
},
DeleteExpr {
column: String::from(r#"col"3"#),
op: Op::Eq,
scalar: Scalar::I64(3),
},
],
};
assert_eq!(
&pred.expr_sql_string(),
r#""col 1"=1 AND "col\\2"=2 AND "col\"3"=3"#
);
}
#[test]
fn test_expr_to_sql_bool() {
let pred = DeletePredicate {
range: TimestampRange { start: 1, end: 2 },
exprs: vec![
DeleteExpr {
column: String::from("col1"),
op: Op::Eq,
scalar: Scalar::Bool(false),
},
DeleteExpr {
column: String::from("col2"),
op: Op::Eq,
scalar: Scalar::Bool(true),
},
],
};
assert_eq!(&pred.expr_sql_string(), r#""col1"=false AND "col2"=true"#);
}
#[test]
fn test_expr_to_sql_i64() {
let pred = DeletePredicate {
range: TimestampRange { start: 1, end: 2 },
exprs: vec![
DeleteExpr {
column: String::from("col1"),
op: Op::Eq,
scalar: Scalar::I64(0),
},
DeleteExpr {
column: String::from("col2"),
op: Op::Eq,
scalar: Scalar::I64(-1),
},
DeleteExpr {
column: String::from("col3"),
op: Op::Eq,
scalar: Scalar::I64(1),
},
DeleteExpr {
column: String::from("col4"),
op: Op::Eq,
scalar: Scalar::I64(i64::MIN),
},
DeleteExpr {
column: String::from("col5"),
op: Op::Eq,
scalar: Scalar::I64(i64::MAX),
},
],
};
assert_eq!(
&pred.expr_sql_string(),
r#""col1"=0 AND "col2"=-1 AND "col3"=1 AND "col4"=-9223372036854775808 AND "col5"=9223372036854775807"#
);
}
#[test]
fn test_expr_to_sql_f64() {
let pred = DeletePredicate {
range: TimestampRange { start: 1, end: 2 },
exprs: vec![
DeleteExpr {
column: String::from("col1"),
op: Op::Eq,
scalar: Scalar::F64(OrderedFloat::from(0.0)),
},
DeleteExpr {
column: String::from("col2"),
op: Op::Eq,
scalar: Scalar::F64(OrderedFloat::from(-0.0)),
},
DeleteExpr {
column: String::from("col3"),
op: Op::Eq,
scalar: Scalar::F64(OrderedFloat::from(1.0)),
},
DeleteExpr {
column: String::from("col4"),
op: Op::Eq,
scalar: Scalar::F64(OrderedFloat::from(f64::INFINITY)),
},
DeleteExpr {
column: String::from("col5"),
op: Op::Eq,
scalar: Scalar::F64(OrderedFloat::from(f64::NEG_INFINITY)),
},
DeleteExpr {
column: String::from("col6"),
op: Op::Eq,
scalar: Scalar::F64(OrderedFloat::from(f64::NAN)),
},
],
};
assert_eq!(
&pred.expr_sql_string(),
r#""col1"=0.0 AND "col2"=-0.0 AND "col3"=1.0 AND "col4"='Infinity' AND "col5"='-Infinity' AND "col6"='NaN'"#
);
}
#[test]
fn test_expr_to_sql_string() {
let pred = DeletePredicate {
range: TimestampRange { start: 1, end: 2 },
exprs: vec![
DeleteExpr {
column: String::from("col1"),
op: Op::Eq,
scalar: Scalar::String(String::from("")),
},
DeleteExpr {
column: String::from("col2"),
op: Op::Eq,
scalar: Scalar::String(String::from("foo")),
},
DeleteExpr {
column: String::from("col3"),
op: Op::Eq,
scalar: Scalar::String(String::from(r#"fo\o"#)),
},
DeleteExpr {
column: String::from("col4"),
op: Op::Eq,
scalar: Scalar::String(String::from(r#"fo'o"#)),
},
],
};
assert_eq!(
&pred.expr_sql_string(),
r#""col1"='' AND "col2"='foo' AND "col3"='fo\\o' AND "col4"='fo\'o'"#
);
}
}

View File

@ -146,7 +146,7 @@ mod tests {
op: Op::Eq,
scalar: Scalar::Bool(true),
},
"foo=true",
r#""foo"=true"#,
);
assert_expr_works(
DeleteExpr {
@ -154,7 +154,7 @@ mod tests {
op: Op::Ne,
scalar: Scalar::I64(-1),
},
"bar!=-1",
r#""bar"!=-1"#,
);
assert_expr_works(
DeleteExpr {
@ -162,7 +162,7 @@ mod tests {
op: Op::Eq,
scalar: Scalar::F64((-1.1).into()),
},
"baz=-1.1",
r#""baz"=-1.1"#,
);
assert_expr_works(
DeleteExpr {
@ -170,7 +170,7 @@ mod tests {
op: Op::Eq,
scalar: Scalar::String("foo".to_string()),
},
"col='foo'",
r#""col"='foo'"#,
);
}