surrealdb-sql 1.1.0

Full type definitions for the SurrealQL query language
Documentation
use super::{
	comment::{mightbespace, shouldbespace},
	IResult,
};
use crate::{Dir, Operator};
use nom::{
	branch::alt,
	bytes::complete::{tag, tag_no_case},
	character::complete::{char, u32, u8},
	combinator::{cut, opt, value},
};

pub fn assigner(i: &str) -> IResult<&str, Operator> {
	alt((
		value(Operator::Equal, char('=')),
		value(Operator::Inc, tag("+=")),
		value(Operator::Dec, tag("-=")),
		value(Operator::Ext, tag("+?=")),
	))(i)
}

pub fn unary(i: &str) -> IResult<&str, Operator> {
	unary_symbols(i)
}

pub fn unary_symbols(i: &str) -> IResult<&str, Operator> {
	let (i, _) = mightbespace(i)?;
	let (i, v) = alt((value(Operator::Neg, tag("-")), value(Operator::Not, tag("!"))))(i)?;
	let (i, _) = mightbespace(i)?;
	Ok((i, v))
}

pub fn binary(i: &str) -> IResult<&str, Operator> {
	alt((binary_symbols, binary_phrases))(i)
}

pub fn binary_symbols(i: &str) -> IResult<&str, Operator> {
	let (i, _) = mightbespace(i)?;
	let (i, v) = alt((
		alt((
			value(Operator::Or, tag("||")),
			value(Operator::And, tag("&&")),
			value(Operator::Tco, tag("?:")),
			value(Operator::Nco, tag("??")),
		)),
		alt((
			value(Operator::Exact, tag("==")),
			value(Operator::NotEqual, tag("!=")),
			value(Operator::AllEqual, tag("*=")),
			value(Operator::AnyEqual, tag("?=")),
			value(Operator::Equal, char('=')),
		)),
		alt((
			value(Operator::NotLike, tag("!~")),
			value(Operator::AllLike, tag("*~")),
			value(Operator::AnyLike, tag("?~")),
			value(Operator::Like, char('~')),
			matches,
			knn,
		)),
		alt((
			value(Operator::LessThanOrEqual, tag("<=")),
			value(Operator::LessThan, char('<')),
			value(Operator::MoreThanOrEqual, tag(">=")),
			value(Operator::MoreThan, char('>')),
			knn,
		)),
		alt((
			value(Operator::Pow, tag("**")),
			value(Operator::Add, char('+')),
			value(Operator::Sub, char('-')),
			value(Operator::Mul, char('*')),
			value(Operator::Mul, char('×')),
			value(Operator::Mul, char('∙')),
			value(Operator::Div, char('/')),
			value(Operator::Div, char('÷')),
		)),
		alt((
			value(Operator::Contain, char('∋')),
			value(Operator::NotContain, char('∌')),
			value(Operator::Inside, char('∈')),
			value(Operator::NotInside, char('∉')),
			value(Operator::ContainAll, char('⊇')),
			value(Operator::ContainAny, char('⊃')),
			value(Operator::ContainNone, char('⊅')),
			value(Operator::AllInside, char('⊆')),
			value(Operator::AnyInside, char('⊂')),
			value(Operator::NoneInside, char('⊄')),
		)),
	))(i)?;
	let (i, _) = mightbespace(i)?;
	Ok((i, v))
}

pub fn binary_phrases(i: &str) -> IResult<&str, Operator> {
	let (i, _) = shouldbespace(i)?;
	let (i, v) = alt((
		alt((
			value(Operator::Or, tag_no_case("OR")),
			value(Operator::And, tag_no_case("AND")),
			value(Operator::NotEqual, tag_no_case("IS NOT")),
			value(Operator::Equal, tag_no_case("IS")),
		)),
		alt((
			value(Operator::ContainAll, tag_no_case("CONTAINSALL")),
			value(Operator::ContainAny, tag_no_case("CONTAINSANY")),
			value(Operator::ContainNone, tag_no_case("CONTAINSNONE")),
			value(Operator::NotContain, tag_no_case("CONTAINSNOT")),
			value(Operator::Contain, tag_no_case("CONTAINS")),
			value(Operator::AllInside, tag_no_case("ALLINSIDE")),
			value(Operator::AnyInside, tag_no_case("ANYINSIDE")),
			value(Operator::NoneInside, tag_no_case("NONEINSIDE")),
			value(Operator::NotInside, tag_no_case("NOTINSIDE")),
			value(Operator::Inside, tag_no_case("INSIDE")),
			value(Operator::Outside, tag_no_case("OUTSIDE")),
			value(Operator::Intersects, tag_no_case("INTERSECTS")),
			value(Operator::NotInside, tag_no_case("NOT IN")),
			value(Operator::Inside, tag_no_case("IN")),
		)),
	))(i)?;
	let (i, _) = shouldbespace(i)?;
	Ok((i, v))
}

pub fn matches(i: &str) -> IResult<&str, Operator> {
	let (i, _) = char('@')(i)?;
	cut(|i| {
		let (i, reference) = opt(u8)(i)?;
		let (i, _) = char('@')(i)?;
		Ok((i, Operator::Matches(reference)))
	})(i)
}

pub fn knn(i: &str) -> IResult<&str, Operator> {
	let (i, _) = char('<')(i)?;
	let (i, k) = u32(i)?;
	let (i, _) = char('>')(i)?;
	Ok((i, Operator::Knn(k)))
}

pub fn dir(i: &str) -> IResult<&str, Dir> {
	alt((value(Dir::Both, tag("<->")), value(Dir::In, tag("<-")), value(Dir::Out, tag("->"))))(i)
}

#[cfg(test)]
mod tests {

	use super::*;

	#[test]
	fn dir_in() {
		let sql = "<-";
		let res = dir(sql);
		let out = res.unwrap().1;
		assert_eq!("<-", format!("{}", out));
	}

	#[test]
	fn dir_out() {
		let sql = "->";
		let res = dir(sql);
		let out = res.unwrap().1;
		assert_eq!("->", format!("{}", out));
	}

	#[test]
	fn dir_both() {
		let sql = "<->";
		let res = dir(sql);
		let out = res.unwrap().1;
		assert_eq!("<->", format!("{}", out));
	}

	#[test]
	fn matches_without_reference() {
		let res = matches("@@");
		let out = res.unwrap().1;
		assert_eq!("@@", format!("{}", out));
		assert_eq!(out, Operator::Matches(None));
	}

	#[test]
	fn matches_with_reference() {
		let res = matches("@12@");
		let out = res.unwrap().1;
		assert_eq!("@12@", format!("{}", out));
		assert_eq!(out, Operator::Matches(Some(12u8)));
	}

	#[test]
	fn matches_with_invalid_reference() {
		let res = matches("@256@");
		res.unwrap_err();
	}

	#[test]
	fn test_knn() {
		let res = knn("<5>");
		assert!(res.is_ok());
		let out = res.unwrap().1;
		assert_eq!("<5>", format!("{}", out));
		assert_eq!(out, Operator::Knn(5));
	}
}