use crate::{BoolTagExprLexicalParse, ParseError, Tag, Tags, Token};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BoolTagExpr(Node);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
pub enum SqlTableInfoError {
#[error("Invalid identifiers: '{0}'")]
InvalidIdentifier(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DbTableInfo {
table_name: String,
id_column: String,
tag_name_column: String,
tag_value_column: String,
}
impl DbTableInfo {
pub fn from(
table_name: &str,
id_column: &str,
tag_name_column: &str,
tag_value_column: &str,
) -> Result<Self, SqlTableInfoError> {
for identifier in [table_name, id_column, tag_name_column, tag_value_column] {
if !is_valid_sql_identifier(identifier) {
Err(SqlTableInfoError::InvalidIdentifier(identifier.to_string()))?;
}
}
Ok(Self {
table_name: table_name.to_string(),
id_column: id_column.to_string(),
tag_name_column: tag_name_column.to_string(),
tag_value_column: tag_value_column.to_string(),
})
}
}
fn is_valid_sql_identifier(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {
chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}
_ => false,
}
}
impl Serialize for BoolTagExpr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bool_expr = self.clone().to_boolean_expression();
serializer.serialize_str(&bool_expr)
}
}
impl<'de> Deserialize<'de> for BoolTagExpr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw_expr = String::deserialize(deserializer)?;
let tree = Self::from(raw_expr);
match tree {
Ok(tree) => Ok(tree),
Err(error) => {
let err_msg = format!("Boolean expressions is invalid: {error}");
Err(serde::de::Error::custom(err_msg))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Node {
And(Box<Node>, Box<Node>),
Or(Box<Node>, Box<Node>),
Not(Box<Node>),
Tag(Tag),
Bool(bool),
}
#[derive(Debug, PartialEq, Clone, Error, Hash, Eq)]
pub enum SyntaxParseError {
#[error("No tags in expression")]
NoTags,
#[error("Invalid opening token")]
InvalidOpeningToken,
#[error("Invalid closing token")]
InvalidClosingToken,
#[error("Unopended brackets")]
UnopenedBrackets,
#[error("Unclosed brackets")]
UnclosedBrackets,
#[error("Invalid sequence of tokens: {0} -> {1}")]
InvalidSequence(Token, Token),
}
pub trait BoolTagExprSyntaxParse<T: BoolTagExprLexicalParse> {
fn syntax_parse(self) -> Result<BoolTagExpr, ParseError>;
}
impl<T: BoolTagExprLexicalParse> BoolTagExprSyntaxParse<T> for T {
fn syntax_parse(self) -> Result<BoolTagExpr, ParseError> {
let lexical_tokens = self.lexical_parse()?;
validate_token_stream(lexical_tokens.tokens().to_owned())?;
Ok(BoolTagExpr(syntax_parse_token_stream(
&mut lexical_tokens.tokens().to_owned(),
)))
}
}
impl BoolTagExpr {
pub fn from<T>(boolean_expr: T) -> Result<Self, ParseError>
where
T: BoolTagExprLexicalParse + BoolTagExprSyntaxParse<T>,
{
boolean_expr.syntax_parse()
}
#[must_use]
pub fn to_boolean_expression(self) -> String {
boolean_expr_tree_to_logical_expr_string(self.0)
}
#[must_use]
pub fn to_sql(self, table_info: &DbTableInfo) -> String {
boolean_expr_tree_to_sql(self.0, table_info)
}
#[must_use]
pub fn into_node(self) -> Node {
self.0
}
#[must_use]
pub fn matches(&self, tags: &Tags) -> bool {
recusively_evaluate_expr_against_tags(&self.0, tags)
}
}
fn recusively_evaluate_expr_against_tags(expr: &Node, tags: &Tags) -> bool {
match expr {
Node::And(l, r) => {
recusively_evaluate_expr_against_tags(l, tags)
&& recusively_evaluate_expr_against_tags(r, tags)
}
Node::Or(l, r) => {
recusively_evaluate_expr_against_tags(l, tags)
|| recusively_evaluate_expr_against_tags(r, tags)
}
Node::Not(e) => !recusively_evaluate_expr_against_tags(e, tags),
Node::Tag(tag) => tags.contains(&tag),
Node::Bool(_) => panic!(),
}
}
fn boolean_expr_tree_to_sql(expr: Node, table_info: &DbTableInfo) -> String {
match expr {
Node::And(l, r) => {
let mut sql_fragment = format!(
"SELECT {} FROM {} WHERE {} IN (",
table_info.id_column, table_info.table_name, table_info.id_column
);
sql_fragment.push_str(&boolean_expr_tree_to_sql(*l, table_info));
sql_fragment.push_str(&format!(") AND {} IN (", table_info.id_column));
sql_fragment.push_str(&boolean_expr_tree_to_sql(*r, table_info));
sql_fragment.push_str(&format!(") GROUP BY {}", table_info.id_column));
sql_fragment
}
Node::Or(l, r) => {
let mut sql_fragment = format!("SELECT {} FROM (", table_info.id_column);
sql_fragment.push_str(&boolean_expr_tree_to_sql(*l, table_info));
sql_fragment.push_str(" UNION ");
sql_fragment.push_str(&boolean_expr_tree_to_sql(*r, table_info));
sql_fragment.push(')');
sql_fragment
}
Node::Not(e) => {
let mut sql_fragment = format!(
"SELECT {} FROM {} WHERE {} NOT IN (",
table_info.id_column, table_info.table_name, table_info.id_column
);
sql_fragment.push_str(&boolean_expr_tree_to_sql(*e, table_info));
sql_fragment.push(')');
sql_fragment
}
Node::Tag(tag) => match tag.name {
None => format!(
"SELECT {} FROM {} WHERE {}='{}'",
table_info.id_column, table_info.table_name, table_info.tag_value_column, tag.value
),
Some(tag_name) => format!(
"SELECT {} FROM {} WHERE {}='{}' AND {}='{}'",
table_info.id_column,
table_info.table_name,
table_info.tag_name_column,
tag_name,
table_info.tag_value_column,
tag.value
),
},
Node::Bool(_) => panic!(),
}
}
fn boolean_expr_tree_to_logical_expr_string(expr: Node) -> String {
match expr {
Node::And(l, r) => {
let mut sql_fragment = String::from("(");
sql_fragment.push_str(&boolean_expr_tree_to_logical_expr_string(*l));
sql_fragment.push_str(" & ");
sql_fragment.push_str(&boolean_expr_tree_to_logical_expr_string(*r));
sql_fragment.push(')');
sql_fragment
}
Node::Or(l, r) => {
let mut sql_fragment = String::from("(");
sql_fragment.push_str(&boolean_expr_tree_to_logical_expr_string(*l));
sql_fragment.push_str(" | ");
sql_fragment.push_str(&boolean_expr_tree_to_logical_expr_string(*r));
sql_fragment.push(')');
sql_fragment
}
Node::Not(e) => {
let mut sql_fragment = String::from("!");
sql_fragment.push_str(&boolean_expr_tree_to_logical_expr_string(*e));
sql_fragment
}
Node::Tag(tag) => match tag.name {
None => format!("{}", tag.value),
Some(tag_name) => format!("({}={})", tag_name, tag.value),
},
Node::Bool(_) => panic!(),
}
}
fn validate_token_stream(mut tokens: Vec<Token>) -> Result<(), SyntaxParseError> {
let mut at_least_1_tag = false;
for token in tokens.clone() {
if let Token::Tag(_) = token {
at_least_1_tag = true;
break;
}
}
if !at_least_1_tag {
return Err(SyntaxParseError::NoTags);
}
let mut opening_bracket_count = 0;
let mut closing_bracket_count = 0;
let mut previous_token = tokens.remove(0);
match &previous_token {
Token::Not | Token::OpenBracket | Token::Tag(_) => Ok(()),
_ => return Err(SyntaxParseError::InvalidOpeningToken),
}?;
for token in tokens {
match previous_token {
Token::OpenBracket => {
opening_bracket_count += 1;
match token.clone() {
Token::OpenBracket | Token::Not | Token::Tag(_) => Ok(()),
Token::CloseBracket => {
return Err(SyntaxParseError::InvalidSequence(
Token::OpenBracket,
Token::CloseBracket,
))
}
Token::And => {
return Err(SyntaxParseError::InvalidSequence(
Token::OpenBracket,
Token::And,
))
}
Token::Or => {
return Err(SyntaxParseError::InvalidSequence(
Token::OpenBracket,
Token::Or,
))
}
}
}
Token::CloseBracket => {
closing_bracket_count += 1;
match token.clone() {
Token::CloseBracket | Token::And | Token::Or => Ok(()),
Token::OpenBracket => {
return Err(SyntaxParseError::InvalidSequence(
Token::CloseBracket,
Token::OpenBracket,
))
}
Token::Not => {
return Err(SyntaxParseError::InvalidSequence(
Token::CloseBracket,
Token::Not,
))
}
Token::Tag(tag) => {
return Err(SyntaxParseError::InvalidSequence(
Token::CloseBracket,
Token::Tag(tag),
))
}
}
}
Token::Not => match token.clone() {
Token::Tag(_) | Token::OpenBracket => Ok(()),
Token::CloseBracket => {
return Err(SyntaxParseError::InvalidSequence(
Token::Not,
Token::CloseBracket,
))
}
Token::Not => {
return Err(SyntaxParseError::InvalidSequence(Token::Not, Token::Not))
}
Token::And => {
return Err(SyntaxParseError::InvalidSequence(Token::Not, Token::And))
}
Token::Or => return Err(SyntaxParseError::InvalidSequence(Token::Not, Token::Or)),
},
Token::And => match token.clone() {
Token::Not | Token::OpenBracket | Token::Tag(_) => Ok(()),
Token::CloseBracket => {
return Err(SyntaxParseError::InvalidSequence(
Token::And,
Token::CloseBracket,
))
}
Token::And => {
return Err(SyntaxParseError::InvalidSequence(Token::And, Token::And))
}
Token::Or => return Err(SyntaxParseError::InvalidSequence(Token::And, Token::Or)),
},
Token::Or => match token.clone() {
Token::Not | Token::OpenBracket | Token::Tag(_) => Ok(()),
Token::CloseBracket => {
return Err(SyntaxParseError::InvalidSequence(
Token::Or,
Token::CloseBracket,
))
}
Token::And => return Err(SyntaxParseError::InvalidSequence(Token::Or, Token::And)),
Token::Or => return Err(SyntaxParseError::InvalidSequence(Token::Or, Token::Or)),
},
Token::Tag(previous_tag) => match token.clone() {
Token::CloseBracket | Token::And | Token::Or => Ok(()),
Token::OpenBracket => {
return Err(SyntaxParseError::InvalidSequence(
Token::Tag(previous_tag),
Token::OpenBracket,
))
}
Token::Not => {
return Err(SyntaxParseError::InvalidSequence(
Token::Tag(previous_tag),
Token::Not,
))
}
Token::Tag(this_tag) => {
println!("{previous_tag} then {this_tag}, is not allowed");
return Err(SyntaxParseError::InvalidSequence(
Token::Tag(previous_tag),
Token::Tag(this_tag),
));
}
},
}?;
previous_token = token;
}
match &previous_token {
Token::CloseBracket => {
closing_bracket_count += 1;
Ok(())
}
Token::Tag(_) => Ok(()),
_ => return Err(SyntaxParseError::InvalidClosingToken),
}?;
if closing_bracket_count > opening_bracket_count {
return Err(SyntaxParseError::UnopenedBrackets);
}
if closing_bracket_count < opening_bracket_count {
return Err(SyntaxParseError::UnclosedBrackets);
}
Ok(())
}
fn syntax_parse_token_stream(tokens: &mut Vec<Token>) -> Node {
let mut expr: Node = recursive_syntax_parse(tokens, None);
loop {
if tokens.is_empty() {
break;
}
expr = recursive_syntax_parse(tokens, Some(expr));
}
expr
}
fn recursive_syntax_parse(tokens: &mut Vec<Token>, expr: Option<Node>) -> Node {
if tokens.is_empty() {
return expr.unwrap();
}
let token = tokens.remove(0);
match token {
Token::OpenBracket => {
recursive_syntax_parse(tokens, expr)
}
Token::CloseBracket => expr.unwrap(),
Token::And => {
let result = recursive_syntax_parse(tokens, None);
Node::And(Box::new(expr.unwrap()), Box::new(result))
}
Token::Or => {
let result = recursive_syntax_parse(tokens, None);
Node::Or(Box::new(expr.unwrap()), Box::new(result))
}
Token::Tag(tag) => recursive_syntax_parse(tokens, Some(Node::Tag(tag))),
Token::Not => {
let result = recursive_syntax_parse(tokens, None);
Node::Not(Box::new(result))
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{TagName, TagValue};
#[test]
fn syntax_parse_empty() -> anyhow::Result<()> {
let a = "";
assert!(a.syntax_parse().err().unwrap() == ParseError::Syntax(SyntaxParseError::NoTags));
let a = "(&)";
assert!(a.syntax_parse().err().unwrap() == ParseError::Syntax(SyntaxParseError::NoTags));
let a = "(& & &) | (& & &)";
assert!(a.syntax_parse().err().unwrap() == ParseError::Syntax(SyntaxParseError::NoTags));
Ok(())
}
#[test]
fn syntax_parse() -> anyhow::Result<()> {
let a = "((nationality=american && scientist) || (=british & scientist)) & !man && person";
assert!(a.syntax_parse().is_err());
let a = "((nationality=american & scientist) | (=british & scientist)) && !man & person";
assert!(a.syntax_parse().is_err());
let a = "((nationality=american & scientist) || (=british & scientist)) & !man && person";
assert!(a.syntax_parse().is_err());
let a = "((nationality=american & scientist) | (=british & scientist)) & !man & person";
assert!(a.syntax_parse().is_ok());
let a = "(a & b";
assert!(a.syntax_parse().is_err());
let a = "(a & b & c)";
assert!(a.syntax_parse().is_ok());
Ok(())
}
#[test]
fn to_sql() -> anyhow::Result<()> {
let table_info = DbTableInfo::from(
&"table_name",
&"id_column",
&"tag_name_column",
&"tag_value_column",
)?;
let a = "((x=a & b) | (c & b)) & !d";
assert!(a.syntax_parse()?.to_sql(&table_info).is_ascii());
Ok(())
}
#[test]
fn to_boolean_expression() -> anyhow::Result<()> {
let a = "((x=a & b) | (c & b)) & !d";
let parsed = a.syntax_parse()?.to_boolean_expression();
let parsed_again = parsed.clone().syntax_parse()?.to_boolean_expression();
assert_eq!(parsed, parsed_again);
Ok(())
}
#[test]
fn matches() -> anyhow::Result<()> {
let expr = BoolTagExpr::from("!d")?;
let tags = Tags::from([Tag::from(None, TagValue::from("d")?)]);
assert!(!expr.matches(&tags));
let expr = BoolTagExpr::from("d")?;
let tags = Tags::from([Tag::from(None, TagValue::from("d")?)]);
assert!(expr.matches(&tags));
let expr_str = "((x=a & b) | (c & b)) & !d";
let expr = BoolTagExpr::from(expr_str)?;
{
let tags = Tags::from([
Tag::from(None, TagValue::from("c")?),
Tag::from(None, TagValue::from("b")?),
]);
assert!(expr.matches(&tags));
}
{
let tags = Tags::from([
Tag::from(None, TagValue::from("c")?),
Tag::from(None, TagValue::from("b")?),
Tag::from(None, TagValue::from("d")?),
]);
assert!(!expr.matches(&tags));
}
{
let tags = Tags::from([
Tag::from(Some(TagName::from("x")?), TagValue::from("a")?),
Tag::from(None, TagValue::from("b")?),
]);
assert!(expr.matches(&tags));
}
{
let tags = Tags::from([
Tag::from(Some(TagName::from("x")?), TagValue::from("a")?),
Tag::from(None, TagValue::from("b")?),
Tag::from(None, TagValue::from("d")?),
]);
assert!(!expr.matches(&tags));
}
Ok(())
}
}