surrealdb-sql 1.1.0

Full type definitions for the SurrealQL query language
Documentation
use super::{
	comment::{mightbespace, shouldbespace},
	common::{closeparentheses, expect_delimited, openparentheses},
	ending,
	error::ExplainResultExt,
	stmt::{create, define, delete, ifelse, insert, output, relate, remove, select, update},
	value::value,
	IResult,
};
use crate::Subquery;
use nom::{
	branch::alt,
	bytes::complete::{tag, tag_no_case},
	combinator::{map, opt, peek},
	sequence::tuple,
};

pub fn subquery(i: &str) -> IResult<&str, Subquery> {
	alt((subquery_ifelse, subquery_other, subquery_value))(i)
}

fn subquery_ifelse(i: &str) -> IResult<&str, Subquery> {
	let (i, v) = map(ifelse, Subquery::Ifelse)(i)?;
	Ok((i, v))
}

fn subquery_value(i: &str) -> IResult<&str, Subquery> {
	expect_delimited(openparentheses, map(value, Subquery::Value), closeparentheses)(i)
}

fn subquery_other(i: &str) -> IResult<&str, Subquery> {
	alt((expect_delimited(openparentheses, subquery_inner, closeparentheses), |i| {
		let (i, v) = subquery_inner(i)?;
		let (i, _) = ending::subquery(i)?;
		let (i, _) = eat_semicolon(i)?;
		Ok((i, v))
	}))(i)
}

fn eat_semicolon(i: &str) -> IResult<&str, ()> {
	let (i, _) = opt(|i| {
		let (i, _) = mightbespace(i)?;
		let (i, _) = tag(";")(i)?;
		let (i, _) = peek(tuple((
			shouldbespace,
			alt((tag_no_case("THEN"), tag_no_case("ELSE"), tag_no_case("END"))),
		)))(i)?;
		Ok((i, ()))
	})(i)?;
	Ok((i, ()))
}

fn subquery_inner(i: &str) -> IResult<&str, Subquery> {
	alt((
		map(output, Subquery::Output),
		map(select, Subquery::Select),
		map(create, Subquery::Create),
		map(update, Subquery::Update),
		map(delete, Subquery::Delete),
		map(relate, Subquery::Relate),
		map(insert, Subquery::Insert),
		map(define, Subquery::Define),
		map(remove, Subquery::Remove),
	))(i)
	.explain("This statement is not allowed in a subquery", disallowed_subquery_statements)
}

fn disallowed_subquery_statements(i: &str) -> IResult<&str, ()> {
	let (i, _) = alt((
		tag_no_case("ANALYZED"),
		tag_no_case("BEGIN"),
		tag_no_case("BREAK"),
		tag_no_case("CONTINUE"),
		tag_no_case("COMMIT"),
		tag_no_case("FOR"),
		tag_no_case("INFO"),
		tag_no_case("KILL"),
		tag_no_case("LIVE"),
		tag_no_case("OPTION"),
		tag_no_case("RELATE"),
		tag_no_case("SLEEP"),
		tag_no_case("THROW"),
		tag_no_case("USE"),
	))(i)?;
	Ok((i, ()))
}

#[cfg(test)]
mod tests {

	use super::*;

	#[test]
	fn subquery_expression_statement() {
		let sql = "(1 + 2 + 3)";
		let res = subquery(sql);
		let out = res.unwrap().1;
		assert_eq!("(1 + 2 + 3)", format!("{}", out))
	}

	#[test]
	fn subquery_ifelse_statement() {
		let sql = "IF true THEN false END";
		let res = subquery(sql);
		let out = res.unwrap().1;
		assert_eq!("IF true THEN false END", format!("{}", out))
	}

	#[test]
	fn subquery_select_statement() {
		let sql = "(SELECT * FROM test)";
		let res = subquery(sql);
		let out = res.unwrap().1;
		assert_eq!("(SELECT * FROM test)", format!("{}", out))
	}

	#[test]
	fn subquery_define_statement() {
		let sql = "(DEFINE EVENT foo ON bar WHEN $event = 'CREATE' THEN (CREATE x SET y = 1))";
		let res = subquery(sql);
		assert!(res.is_ok());
		let out = res.unwrap().1;
		assert_eq!(
			"(DEFINE EVENT foo ON bar WHEN $event = 'CREATE' THEN (CREATE x SET y = 1))",
			format!("{}", out)
		)
	}

	#[test]
	fn subquery_remove_statement() {
		let sql = "(REMOVE EVENT foo_event ON foo)";
		let res = subquery(sql);
		assert!(res.is_ok());
		let out = res.unwrap().1;
		assert_eq!("(REMOVE EVENT foo_event ON foo)", format!("{}", out))
	}
}