feat: allow to format delete predicates as SQL strings
This is required to feed them back into the gRPC delete API.pull/24376/head
parent
50e9e02ff7
commit
a9ec0720b2
|
|
@ -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'"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'"#,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue