use chrono::FixedOffset;
use crate::{
ast::{visit::AstVisitor, *},
is_valid_identifier,
};
fn is_comparison_operator(op: BinaryOperator) -> bool {
match op {
BinaryOperator::And | BinaryOperator::Or => false,
_ => true,
}
}
fn need_parens(parent: &Expression, child: &Expression) -> bool {
match (parent, child) {
(Expression::BinaryExpression(parent), Expression::BinaryExpression(child)) => {
if parent.op.precedence() == child.op.precedence() {
is_comparison_operator(parent.op) && is_comparison_operator(child.op)
} else {
parent.op.precedence() > child.op.precedence()
}
}
(Expression::UnaryExpression(_), Expression::BinaryExpression(_)) => true,
(_, _) => false,
}
}
struct QueryWriter<'a> {
query: String,
stack: Vec<&'a Expression>,
}
impl<'a> QueryWriter<'a> {
fn new() -> QueryWriter<'a> {
QueryWriter {
query: String::new(),
stack: vec![],
}
}
fn push_escape_str(&mut self, str: &str) {
self.query.push_str("\"");
for c in str.chars() {
match c {
'"' => {
self.query.push_str("\\\"");
}
'\\' => {
self.query.push_str("\\\\");
}
'\n' => {
self.query.push_str("\\n");
}
'\r' => {
self.query.push_str("\\r");
}
'\t' => {
self.query.push_str("\\t");
}
'\u{1B}' => {
self.query.push_str("\\");
}
'\u{0}'..='\u{1F}' => {
for c in c.escape_unicode() {
self.query.push(c);
}
}
c => {
self.query.push(c);
}
}
}
self.query.push_str("\"");
}
}
impl<'a> AstVisitor<'a> for QueryWriter<'a> {
fn visit_expression(&mut self, expression: &'a Expression) {
let parent = self.stack.last().copied();
self.stack.push(expression);
let need_parens = parent.map_or(false, |parent| need_parens(parent, expression));
if need_parens {
self.query.push_str("(");
}
visit::walk_expression(self, expression);
if need_parens {
self.query.push_str(")");
}
self.stack.pop();
}
fn visit_unary_operator(&mut self, op: &'a UnaryOperator) {
match *op {
UnaryOperator::Not => self.query.push_str("not "),
}
}
fn visit_binary_operator(&mut self, op: &'a BinaryOperator) {
let parent = self.stack.last().copied();
let is_time = parent.map_or(false, |parent| match parent {
Expression::BinaryExpression(expr) => match expr.rhs {
Expression::Value(Value::Date(_)) => true,
Expression::Value(Value::DateTime(_)) => true,
Expression::Value(Value::NaiveDateTime(_)) => true,
Expression::Value(Value::RelativeTime(_)) => true,
_ => false,
},
_ => false,
});
match *op {
BinaryOperator::Eq => self.query.push_str(" == "),
BinaryOperator::Ne => self.query.push_str(" != "),
BinaryOperator::Lt => self.query.push_str(" < "),
BinaryOperator::Gt => self.query.push_str(" > "),
BinaryOperator::Lte if is_time => self.query.push_str(" before "),
BinaryOperator::Lte => self.query.push_str(" <= "),
BinaryOperator::Gte if is_time => self.query.push_str(" after "),
BinaryOperator::Gte => self.query.push_str(" >= "),
BinaryOperator::Match => self.query.push_str(" match "),
BinaryOperator::IMatch => self.query.push_str(" imatch "),
BinaryOperator::And => self.query.push_str(" and "),
BinaryOperator::Or => self.query.push_str(" or "),
}
}
fn visit_absolute_path(&mut self, path: &AbsolutePath) {
for path_element in path {
if is_valid_identifier(&path_element) {
self.query.push_str(".");
self.query.push_str(&path_element);
} else {
self.query.push_str(".");
self.push_escape_str(&path_element);
}
}
}
fn visit_wildcard_path(&mut self, path: &WildcardPath) {
for path_element in &path.0 {
match path_element {
PathElement::Field(field) => {
if is_valid_identifier(&field) {
self.query.push_str(".");
self.query.push_str(&field);
} else {
self.query.push_str(".");
self.push_escape_str(&field);
}
}
PathElement::Wildcard => self.query.push_str(".*"),
PathElement::RecursiveWildcard => self.query.push_str(".**"),
}
}
}
fn visit_value(&mut self, value: &Value) {
match *value {
Value::Null => {
self.query.push_str("null");
}
Value::Bool(bool) => {
if bool {
self.query.push_str("true");
} else {
self.query.push_str("false");
}
}
Value::Int(num) => {
self.query.push_str(&num.to_string());
}
Value::Float(num) => {
self.query.push_str(&num.to_string());
}
Value::String(ref str) => {
self.push_escape_str(str);
}
Value::Date(ref date) => self.query.push_str(&date.format("%Y-%m-%d").to_string()),
Value::DateTime(ref date) => {
if date.offset() == &FixedOffset::east(0) {
self
.query
.push_str(&date.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string())
} else {
self
.query
.push_str(&date.format("%Y-%m-%dT%H:%M:%S%.f%:z").to_string())
}
}
Value::NaiveDateTime(ref date) => self
.query
.push_str(&date.format("%Y-%m-%dT%H:%M:%S%.f").to_string()),
Value::RelativeTime(ref date) => match date {
RelativeTime::Now => self.query.push_str("now"),
RelativeTime::Duration(duration, anchor) => {
match duration {
Duration::Seconds(v) => {
self.query.push_str(&v.to_string());
self.query.push_str(" second");
if v.abs() > 1 {
self.query.push_str("s");
}
}
Duration::Minutes(v) => {
self.query.push_str(&v.to_string());
self.query.push_str(" minute");
if v.abs() > 1 {
self.query.push_str("s");
}
}
Duration::Hours(v) => {
self.query.push_str(&v.to_string());
self.query.push_str(" hour");
if v.abs() > 1 {
self.query.push_str("s");
}
}
Duration::Days(v) => {
self.query.push_str(&v.to_string());
self.query.push_str(" day");
if v.abs() > 1 {
self.query.push_str("s");
}
}
Duration::Weeks(v) => {
self.query.push_str(&v.to_string());
self.query.push_str(" week");
if v.abs() > 1 {
self.query.push_str("s");
}
}
Duration::Months(v) => {
self.query.push_str(&v.to_string());
self.query.push_str(" month");
if v.abs() > 1 {
self.query.push_str("s");
}
}
Duration::Years(v) => {
self.query.push_str(&v.to_string());
self.query.push_str(" year");
if v.abs() > 1 {
self.query.push_str("s");
}
}
}
self.query.push_str(" ");
match anchor {
TimeAnchor::Ago => self.query.push_str("ago"),
}
}
},
}
}
}
impl ToString for WildcardPath {
fn to_string(&self) -> String {
let mut writer = QueryWriter::new();
writer.visit_wildcard_path(self);
writer.query
}
}
impl ToString for Expression {
fn to_string(&self) -> String {
let mut writer = QueryWriter::new();
writer.visit_expression(self);
writer.query
}
}
pub fn encode_absolute_path(path: &AbsolutePath) -> String {
let mut writer = QueryWriter::new();
writer.visit_absolute_path(path);
writer.query
}
#[cfg(test)]
mod test {
use crate::parser::parse_filter;
fn test_query_format(input: &str, expected: &str) {
let (query, errors) = parse_filter(input);
assert_eq!(errors, vec![]);
let output = query.to_string();
assert_eq!(output, expected);
}
fn test_query(input: &str) {
let (query, errors) = parse_filter(input);
assert_eq!(errors, vec![]);
let output = query.to_string();
assert_eq!(output, input);
}
#[test]
fn test_precedence() {
test_query(".a and .b or .c and .d");
test_query(".a and (.b or .c) and .d");
test_query(".a or .level < 5 or .b");
test_query("1 < 2 == 2 > 3");
test_query_format("1 == 2 == false", "(1 == 2) == false");
test_query("false == (1 == 2)");
test_query("false == not (1 == 2)");
test_query("not .a or .b");
test_query(".a and .b match \"*\\*foo\"");
test_query(".a and .b imatch \"foo?\\?\"");
}
#[test]
fn test_types() {
test_query(".a");
test_query(".data.a == null");
test_query(".data.a != null");
test_query(".data.a and true");
test_query(".data.a or false");
test_query(".level == .data.a");
test_query(".data.a == .level");
test_query("42 == .data.a");
test_query(".data.a == 42");
test_query("3.14 == .data.a");
test_query(".data.a == 3.14");
test_query("42 == 3.14");
test_query(".message == .data.a");
test_query(".data.a == .message");
test_query("42 <= 3.14");
test_query(".time before 2020-01-01");
}
#[test]
fn test_time() {
test_query("2020-01-02");
test_query_format("2020-01-02T03:04", "2020-01-02T03:04:00");
test_query("2020-01-02T03:04:05");
test_query_format("2020-01-02T03:04:05.000", "2020-01-02T03:04:05");
test_query("2020-01-02T03:04:05.123");
test_query("2020-01-02T03:04:05.123456");
test_query("2020-01-02T03:04:05.123456789");
test_query_format("2020-01-02T03:04Z", "2020-01-02T03:04:00Z");
test_query("2020-01-02T03:04:05Z");
test_query_format("2020-01-02T03:04:05.000Z", "2020-01-02T03:04:05Z");
test_query("2020-01-02T03:04:05.123Z");
test_query("2020-01-02T03:04:05.123456Z");
test_query("2020-01-02T03:04:05.123456789Z");
test_query_format("2020-01-02T03:04+05:00", "2020-01-02T03:04:00+05:00");
test_query("2020-01-02T03:04:05+05:00");
test_query_format("2020-01-02T03:04:05.000+05:00", "2020-01-02T03:04:05+05:00");
test_query("2020-01-02T03:04:05.123+05:00");
test_query("2020-01-02T03:04:05.123456+05:00");
test_query("2020-01-02T03:04:05.123456789+05:00");
test_query_format("2020-01-02T03:04-05:00", "2020-01-02T03:04:00-05:00");
test_query("2020-01-02T03:04:05-05:00");
test_query_format("2020-01-02T03:04:05.000-05:00", "2020-01-02T03:04:05-05:00");
test_query("2020-01-02T03:04:05.123-05:00");
test_query("2020-01-02T03:04:05.123456-05:00");
test_query("2020-01-02T03:04:05.123456789-05:00");
test_query("now");
test_query("1 second ago");
test_query("2 seconds ago");
test_query("1 minute ago");
test_query("2 minutes ago");
test_query("1 hour ago");
test_query("2 hours ago");
test_query("1 day ago");
test_query("2 days ago");
test_query("1 week ago");
test_query("2 weeks ago");
test_query("1 month ago");
test_query("2 months ago");
test_query("1 year ago");
test_query("2 years ago");
}
#[test]
fn test_paths() {
test_query(".a.b.c.d");
test_query_format(".a.\"b\".\"c\"", ".a.b.c");
test_query(".a.\"b c\"");
test_query(".a.\"b \\\"foo\\\" c\"");
test_query(".a.\"b \\\\\\\"foo\\\\\\\" c\"");
test_query(".a.\"b\\n\\r\\t\\u{10}c\"");
}
}