reifydb-rql 0.4.12

ReifyDB Query Language (RQL) parser and AST
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2025 ReifyDB

use crate::{
	Result,
	ast::{ast::AstFilter, parse::Parser},
	token::keyword::Keyword,
};

impl<'bump> Parser<'bump> {
	pub(crate) fn parse_filter(&mut self) -> Result<AstFilter<'bump>> {
		let (token, node, rql) = self.parse_keyword_with_optional_braces_single(Keyword::Filter)?;
		Ok(AstFilter {
			token,
			node,
			rql,
		})
	}
}

#[cfg(test)]
pub mod tests {
	use crate::{
		ast::{
			ast::{Ast, InfixOperator},
			parse::Parser,
		},
		bump::Bump,
		token::{keyword::Keyword, token::TokenKind, tokenize},
	};

	#[test]
	fn test_simple_comparison() {
		let bump = Bump::new();
		let source = "filter {price > 100}";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		assert_eq!(filter.token.kind, TokenKind::Keyword(Keyword::Filter));

		let node = filter.node.as_infix();
		assert_eq!(node.left.as_identifier().text(), "price");
		assert!(matches!(node.operator, InfixOperator::GreaterThan(_)));
		assert_eq!(node.right.as_literal_number().value(), "100");
	}

	#[test]
	fn test_nested_expression() {
		let bump = Bump::new();
		let source = "filter {(price + fee) > 100}";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::GreaterThan(_)));
		assert_eq!(node.right.as_literal_number().value(), "100");

		let nested = node.left.as_tuple().nodes[0].as_infix();
		assert_eq!(nested.left.as_identifier().text(), "price");
		assert!(matches!(nested.operator, InfixOperator::Add(_)));
		assert_eq!(nested.right.as_identifier().text(), "fee");
	}

	#[test]
	fn test_filter_without_braces() {
		let bump = Bump::new();
		let source = "filter price > 100";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert_eq!(node.left.as_identifier().text(), "price");
		assert!(matches!(node.operator, InfixOperator::GreaterThan(_)));
		assert_eq!(node.right.as_literal_number().value(), "100");
	}

	#[test]
	fn test_keyword() {
		let bump = Bump::new();
		let source = "filter {value > 100}";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert_eq!(node.left.as_identifier().text(), "value");
		assert!(matches!(node.operator, InfixOperator::GreaterThan(_)));
		assert_eq!(node.right.as_literal_number().value(), "100");
	}

	#[test]
	fn test_logical_and() {
		let bump = Bump::new();
		let source = "filter {price > 100 and qty < 50}";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::And(_)));

		let left = node.left.as_infix();
		assert_eq!(left.left.as_identifier().text(), "price");
		assert!(matches!(left.operator, InfixOperator::GreaterThan(_)));
		assert_eq!(left.right.as_literal_number().value(), "100");

		let right = node.right.as_infix();
		assert_eq!(right.left.as_identifier().text(), "qty");
		assert!(matches!(right.operator, InfixOperator::LessThan(_)));
		assert_eq!(right.right.as_literal_number().value(), "50");
	}

	#[test]
	fn test_logical_or() {
		let bump = Bump::new();
		let source = "filter {active == true or premium == true}";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::Or(_)));

		let left = node.left.as_infix();
		assert_eq!(left.left.as_identifier().text(), "active");
		assert!(matches!(left.operator, InfixOperator::Equal(_)));

		let right = node.right.as_infix();
		assert_eq!(right.left.as_identifier().text(), "premium");
		assert!(matches!(right.operator, InfixOperator::Equal(_)));
	}

	#[test]
	fn test_logical_xor() {
		let bump = Bump::new();
		let source = "filter {active == true xor guest == true}";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::Xor(_)));

		let left = node.left.as_infix();
		assert_eq!(left.left.as_identifier().text(), "active");
		assert!(matches!(left.operator, InfixOperator::Equal(_)));

		let right = node.right.as_infix();
		assert_eq!(right.left.as_identifier().text(), "guest");
		assert!(matches!(right.operator, InfixOperator::Equal(_)));
	}

	#[test]
	fn test_comptokenize_logical_chain() {
		let bump = Bump::new();
		let source = "filter {active == true and price > 100 or premium == true}";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::Or(_)));

		let left_and = node.left.as_infix();
		assert!(matches!(left_and.operator, InfixOperator::And(_)));

		let right_or = node.right.as_infix();
		assert_eq!(right_or.left.as_identifier().text(), "premium");
		assert!(matches!(right_or.operator, InfixOperator::Equal(_)));
	}

	#[test]
	fn test_filter_with_braces() {
		let bump = Bump::new();
		let source = "filter { price > 100 }";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		assert_eq!(filter.token.kind, TokenKind::Keyword(Keyword::Filter));

		let node = filter.node.as_infix();
		assert_eq!(node.left.as_identifier().text(), "price");
		assert!(matches!(node.operator, InfixOperator::GreaterThan(_)));
		assert_eq!(node.right.as_literal_number().value(), "100");
	}

	#[test]
	fn test_filter_comptokenize_expression_with_braces() {
		let bump = Bump::new();
		let source = "filter { (price + fee) > 100 and active == true }";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::And(_)));

		let left = node.left.as_infix();
		assert!(matches!(left.operator, InfixOperator::GreaterThan(_)));
		assert_eq!(left.right.as_literal_number().value(), "100");

		let nested = left.left.as_tuple().nodes[0].as_infix();
		assert_eq!(nested.left.as_identifier().text(), "price");
		assert!(matches!(nested.operator, InfixOperator::Add(_)));
		assert_eq!(nested.right.as_identifier().text(), "fee");

		let right = node.right.as_infix();
		assert_eq!(right.left.as_identifier().text(), "active");
		assert!(matches!(right.operator, InfixOperator::Equal(_)));
	}

	#[test]
	fn test_filter_without_braces_logical() {
		let bump = Bump::new();
		let source = "filter active == true and price > 100";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::And(_)));

		let left = node.left.as_infix();
		assert_eq!(left.left.as_identifier().text(), "active");

		let right = node.right.as_infix();
		assert_eq!(right.left.as_identifier().text(), "price");
	}

	#[test]
	fn test_filter_with_braces_logical_operators() {
		let bump = Bump::new();
		let source = "filter { active == true or premium == true }";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let filter = parser.parse_filter().unwrap();

		let node = filter.node.as_infix();
		assert!(matches!(node.operator, InfixOperator::Or(_)));

		let left = node.left.as_infix();
		assert_eq!(left.left.as_identifier().text(), "active");
		assert!(matches!(left.operator, InfixOperator::Equal(_)));

		let right = node.right.as_infix();
		assert_eq!(right.left.as_identifier().text(), "premium");
		assert!(matches!(right.operator, InfixOperator::Equal(_)));
	}

	#[test]
	fn test_filter_empty_braces() {
		let bump = Bump::new();
		let source = "filter { }";
		let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
		let mut parser = Parser::new(&bump, source, tokens);
		let result = parser.parse_filter().unwrap();
		assert!(matches!(&*result.node, Ast::Nop));
	}
}