use crate::parser::{
ParseError, Phase, QueryParser,
ast::{
Collection, CollectionsParser, Column, GroupBy, HavingParser, Identifier, Join,
LimitAndOffsetParser, OrderBy, Predicate, ProjectionParser, WhereParser,
},
};
#[derive(Default, Clone, PartialEq)]
pub struct Query {
pub projection: Vec<Identifier>,
pub collections: Vec<Collection>,
pub joins: Vec<Join>,
pub criteria: Option<Predicate>,
pub group_by: Vec<Column>,
pub having: Option<Predicate>,
pub order_by: Vec<OrderBy>,
pub limit: Option<i64>,
pub offset: Option<i64>,
}
impl Query {
pub fn parse(parser: &mut QueryParser) -> Result<Self, ParseError> {
parser.next_non_whitespace();
let mut query = Query::default();
while parser.phase != Phase::EOF {
match parser.phase {
Phase::Projection => query.projection = ProjectionParser::parse(parser)?,
Phase::Collections => query.collections = CollectionsParser::parse(parser)?,
Phase::Joins => query.joins = Join::parse(parser)?,
Phase::Criteria => query.criteria = Some(WhereParser::parse(parser)?),
Phase::Aggregates => query.group_by = GroupBy::parse(parser)?,
Phase::Having => query.having = Some(HavingParser::parse(parser)?),
Phase::OrderBy => query.order_by = OrderBy::parse(parser)?,
Phase::LimitAndOffset => {
let (limit, offset) = LimitAndOffsetParser::parse(parser)?;
query.limit = limit;
query.offset = offset;
}
Phase::EOF => todo!(),
}
}
Ok(query)
}
}
impl TryFrom<&str> for Query {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut parser = QueryParser::new(value);
Query::parse(&mut parser)
}
}
use std::fmt;
impl fmt::Display for Query {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let proj = self
.projection
.iter()
.map(|p| format!("{:?}", p))
.collect::<Vec<_>>()
.join(", ");
let cols = self
.collections
.iter()
.map(|c| format!("{:?}", c))
.collect::<Vec<_>>()
.join(", ");
let joins = self
.joins
.iter()
.map(|j| format!("{:?}", j))
.collect::<Vec<_>>()
.join(", ");
let crit = match &self.criteria {
Some(c) => format!("{:?}", c),
None => "None".to_string(),
};
let group = self
.group_by
.iter()
.map(|g| format!("{:?}", g))
.collect::<Vec<_>>()
.join(", ");
let having = match &self.having {
Some(h) => format!("{:?}", h),
None => "None".to_string(),
};
let order = self
.order_by
.iter()
.map(|o| format!("{:?}", o))
.collect::<Vec<_>>()
.join(", ");
write!(
f,
"Query(projection=[{}], collections=[{}], joins=[{}], criteria={}, group_by=[{}], having={}, order_by=[{}], limit={:?}, offset={:?})",
proj, cols, joins, crit, group, having, order, self.limit, self.offset
)
}
}
impl fmt::Debug for Query {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Query({})", self)
}
}
#[cfg(test)]
mod tests {
use crate::parser::ast::Query;
#[test]
pub fn test_query() {
let text = r#"
SELECT b.*, a.full_name as name, COUNT(*) as TotBy, *
FROM TableA A, OtherTable
INNER JOIN TableB B ON A.id = B.id
LEFT JOIN TableC C ON C.id = B.q_id
WHERE A.Age > 16 AND (B.city = 'Porto' OR B.city like "Matosinhos")
GROUP BY a.full_name
HAVING COUNT(*) > 3
ORDER BY b.description DESC
OFFSET 60
LIMIT 20
"#;
let query = Query::try_from(text).expect("Failed to parse predicate");
assert_eq!(query.projection.len(), 4);
assert_eq!(query.collections.len(), 2);
assert_eq!(query.joins.len(), 2);
assert!(query.criteria.is_some());
assert_eq!(query.group_by.len(), 1);
assert!(query.having.is_some());
assert_eq!(query.order_by.len(), 1);
assert_eq!(query.offset.unwrap(), 60);
assert_eq!(query.limit.unwrap(), 20);
}
#[test]
fn try_from_parses_minimal_query() {
let query = Query::try_from("SELECT * FROM people").expect("query should parse");
assert_eq!(query.projection.len(), 1);
assert_eq!(query.collections.len(), 1);
assert!(query.joins.is_empty());
assert!(query.criteria.is_none());
assert!(query.group_by.is_empty());
assert!(query.having.is_none());
assert!(query.order_by.is_empty());
assert_eq!(query.limit, None);
assert_eq!(query.offset, None);
}
#[test]
fn try_from_parses_from_subquery_with_alias() {
let query = Query::try_from("SELECT p.name FROM (SELECT name FROM people) p")
.expect("subquery should parse");
assert_eq!(query.projection.len(), 1);
assert_eq!(query.collections.len(), 1);
match &query.collections[0] {
crate::parser::ast::Collection::Query { query, alias } => {
assert_eq!(alias.as_deref(), Some("p"));
assert_eq!(query.projection.len(), 1);
assert_eq!(query.collections.len(), 1);
}
other => panic!("expected query collection, got {other:?}"),
}
}
#[test]
fn try_from_rejects_from_subquery_without_alias() {
let err = Query::try_from("SELECT name FROM (SELECT name FROM people)");
assert!(err.is_err(), "FROM subqueries require aliases");
}
#[test]
fn display_and_debug_include_all_query_sections() {
let query = Query::try_from("SELECT * FROM people LIMIT 5").expect("query should parse");
let display = query.to_string();
assert!(display.starts_with("Query(projection=["));
assert!(display.contains("collections=["));
assert!(display.contains("joins=[]"));
assert!(display.contains("criteria=None"));
assert!(display.contains("group_by=[]"));
assert!(display.contains("having=None"));
assert!(display.contains("order_by=[]"));
assert!(display.contains("limit=Some(5)"));
assert!(display.contains("offset=None"));
assert_eq!(format!("{:?}", query), format!("Query({display})"));
}
#[test]
fn display_includes_present_optional_sections() {
let query = Query::try_from(
r#"
SELECT people.city, COUNT(*) AS total
FROM people
WHERE people.age > 20
GROUP BY people.city
HAVING COUNT(*) > 1
ORDER BY people.city DESC
OFFSET 2
LIMIT 3
"#,
)
.expect("query should parse");
let display = query.to_string();
assert!(display.contains("criteria=Predicate"));
assert!(display.contains("having=Predicate"));
assert!(display.contains("order_by=["));
assert!(display.contains("limit=Some(3)"));
assert!(display.contains("offset=Some(2)"));
}
}