use {
super::{
BuildAggregationInputPlan, BuildAggregationPlan, BuildHavingPlan, BuildProjectInputPlan,
BuildSelect, DistinctNode,
},
crate::{
ast::Select,
plan::{AggregationPlan, HavingPlan, ProjectInputPlan},
query_builder::{
ExprNode, FilterNode, GroupByNode, InnerHashJoinNode, InnerJoinConditionNode,
InnerNestedLoopJoinNode, LeftOuterHashJoinNode, LeftOuterJoinConditionNode,
LeftOuterNestedLoopJoinNode, LimitNode, OffsetNode, OrderByExprList, ProjectNode,
QueryNode, SelectItemList, SelectNode, SelectOrderByNode, SourceNode,
},
result::Result,
},
};
#[derive(Clone, Debug)]
pub(super) enum PrevNode<'a> {
Select(SelectNode<'a>),
InnerNestedLoop(Box<InnerNestedLoopJoinNode<'a>>),
LeftOuterNestedLoop(Box<LeftOuterNestedLoopJoinNode<'a>>),
InnerHash(Box<InnerHashJoinNode<'a>>),
LeftOuterHash(Box<LeftOuterHashJoinNode<'a>>),
InnerCondition(Box<InnerJoinConditionNode<'a>>),
LeftOuterCondition(Box<LeftOuterJoinConditionNode<'a>>),
Filter(FilterNode<'a>),
GroupBy(GroupByNode<'a>),
}
impl PrevNode<'_> {
fn build_aggregation_plan(self) -> Result<AggregationPlan> {
match self {
Self::Select(node) => node.build_aggregation_input_plan(),
Self::InnerNestedLoop(node) => node.build_aggregation_input_plan(),
Self::LeftOuterNestedLoop(node) => node.build_aggregation_input_plan(),
Self::InnerHash(node) => node.build_aggregation_input_plan(),
Self::LeftOuterHash(node) => node.build_aggregation_input_plan(),
Self::InnerCondition(node) => node.build_aggregation_input_plan(),
Self::LeftOuterCondition(node) => node.build_aggregation_input_plan(),
Self::Filter(node) => node.build_aggregation_input_plan(),
Self::GroupBy(node) => return node.build_aggregation_plan(),
}
.map(|input| AggregationPlan {
input,
group_by: Vec::new(),
aggregate_slots: Vec::new(),
})
}
}
impl BuildSelect for PrevNode<'_> {
fn build_select(self) -> Result<Select> {
match self {
Self::Select(node) => node.build_select(),
Self::InnerNestedLoop(node) => node.build_select(),
Self::LeftOuterNestedLoop(node) => node.build_select(),
Self::InnerHash(node) => node.build_select(),
Self::LeftOuterHash(node) => node.build_select(),
Self::InnerCondition(node) => node.build_select(),
Self::LeftOuterCondition(node) => node.build_select(),
Self::Filter(node) => node.build_select(),
Self::GroupBy(node) => node.build_select(),
}
}
}
impl<'a> From<SelectNode<'a>> for PrevNode<'a> {
fn from(node: SelectNode<'a>) -> Self {
Self::Select(node)
}
}
impl<'a> From<InnerNestedLoopJoinNode<'a>> for PrevNode<'a> {
fn from(node: InnerNestedLoopJoinNode<'a>) -> Self {
Self::InnerNestedLoop(Box::new(node))
}
}
impl<'a> From<LeftOuterNestedLoopJoinNode<'a>> for PrevNode<'a> {
fn from(node: LeftOuterNestedLoopJoinNode<'a>) -> Self {
Self::LeftOuterNestedLoop(Box::new(node))
}
}
impl<'a> From<InnerHashJoinNode<'a>> for PrevNode<'a> {
fn from(node: InnerHashJoinNode<'a>) -> Self {
Self::InnerHash(Box::new(node))
}
}
impl<'a> From<LeftOuterHashJoinNode<'a>> for PrevNode<'a> {
fn from(node: LeftOuterHashJoinNode<'a>) -> Self {
Self::LeftOuterHash(Box::new(node))
}
}
impl<'a> From<InnerJoinConditionNode<'a>> for PrevNode<'a> {
fn from(node: InnerJoinConditionNode<'a>) -> Self {
Self::InnerCondition(Box::new(node))
}
}
impl<'a> From<LeftOuterJoinConditionNode<'a>> for PrevNode<'a> {
fn from(node: LeftOuterJoinConditionNode<'a>) -> Self {
Self::LeftOuterCondition(Box::new(node))
}
}
impl<'a> From<FilterNode<'a>> for PrevNode<'a> {
fn from(node: FilterNode<'a>) -> Self {
Self::Filter(node)
}
}
impl<'a> From<GroupByNode<'a>> for PrevNode<'a> {
fn from(node: GroupByNode<'a>) -> Self {
Self::GroupBy(node)
}
}
#[derive(Clone, Debug)]
pub struct HavingNode<'a> {
prev_node: PrevNode<'a>,
expr: ExprNode<'a>,
}
impl<'a> HavingNode<'a> {
pub(super) fn new<N: Into<PrevNode<'a>>, T: Into<ExprNode<'a>>>(prev_node: N, expr: T) -> Self {
Self {
prev_node: prev_node.into(),
expr: expr.into(),
}
}
pub fn offset<T: Into<ExprNode<'a>>>(self, expr: T) -> OffsetNode<'a> {
OffsetNode::new(self, expr)
}
pub fn limit<T: Into<ExprNode<'a>>>(self, expr: T) -> LimitNode<'a> {
LimitNode::new(self, expr)
}
pub fn project<T: Into<SelectItemList<'a>>>(self, select_items: T) -> ProjectNode<'a> {
ProjectNode::new(self, select_items)
}
pub fn order_by<T: Into<OrderByExprList<'a>>>(self, expr_list: T) -> SelectOrderByNode<'a> {
SelectOrderByNode::new(self, expr_list)
}
pub fn distinct(self) -> DistinctNode<'a> {
DistinctNode::new(self)
}
pub fn alias_as(self, table_alias: &'a str) -> SourceNode<'a> {
QueryNode::HavingNode(self).alias_as(table_alias)
}
}
impl BuildHavingPlan for HavingNode<'_> {
fn build_having_plan(self) -> Result<HavingPlan> {
Ok(HavingPlan {
input: self.prev_node.build_aggregation_plan()?,
expr: self.expr.build_expr_plan()?,
})
}
}
impl BuildProjectInputPlan for HavingNode<'_> {
fn build_project_input_plan(self) -> Result<ProjectInputPlan> {
self.build_having_plan().map(ProjectInputPlan::Having)
}
}
impl BuildSelect for HavingNode<'_> {
fn build_select(self) -> Result<Select> {
let mut select = self.prev_node.build_select()?;
select.having = Some(self.expr.build_expr()?);
Ok(select)
}
}
#[cfg(test)]
mod tests {
use {
crate::{
data::Value,
plan::{
AggregationInputPlan, AggregationPlan, ExprPlan, HashJoinInputPlan, HashJoinPlan,
HavingPlan, InnerJoinInputPlan, InnerJoinPlan, ProjectInputPlan, ProjectPlan,
ProjectionPlan, QueryPlan, SelectItemPlan, SourcePlan, StatementPlan,
TableAccessPlan, TableSourcePlan,
},
query_builder::{
Build, QueryBuilderError, select::BuildQuery, table, test_query_builder,
},
result::Error,
},
pretty_assertions::assert_eq,
};
#[test]
fn prev_nodes() {
let actual = table("Foo").select().having("TRUE");
let expected = "SELECT * FROM Foo HAVING TRUE";
test_query_builder(actual, expected);
let actual = table("Foo").select().join("Bar").having("TRUE");
let expected = "SELECT * FROM Foo JOIN Bar HAVING TRUE";
test_query_builder(actual, expected);
let actual = table("Foo")
.select()
.join("Bar")
.on("Foo.id = Bar.id")
.having("TRUE");
let expected = "SELECT * FROM Foo JOIN Bar ON Foo.id = Bar.id HAVING TRUE";
test_query_builder(actual, expected);
let actual = table("Foo").select().filter("id > 1").having("TRUE");
let expected = "SELECT * FROM Foo WHERE id > 1 HAVING TRUE";
test_query_builder(actual, expected);
}
#[test]
fn plan_only_hash_join() {
let actual = table("Foo")
.select()
.join("Bar")
.hash_executor("Foo.id", "Bar.id")
.having("TRUE")
.build_query()
.map(|_| ());
let expected = Err(Error::QueryBuilder(
QueryBuilderError::HashJoinExecutorRequiresPlan,
));
assert_eq!(actual, expected);
let actual = table("Foo")
.select()
.join("Bar")
.hash_executor("Foo.id", "Bar.id")
.having("TRUE")
.build();
let expected = Ok(StatementPlan::Query(QueryPlan::Project(ProjectPlan {
input: ProjectInputPlan::Having(HavingPlan {
input: AggregationPlan {
input: AggregationInputPlan::InnerJoin(Box::new(InnerJoinPlan {
input: InnerJoinInputPlan::Hash(HashJoinPlan {
input: HashJoinInputPlan::Source(SourcePlan::Table(TableSourcePlan {
name: "Foo".to_owned(),
alias: None,
access: TableAccessPlan::FullScan,
})),
right: SourcePlan::Table(TableSourcePlan {
name: "Bar".to_owned(),
alias: None,
access: TableAccessPlan::FullScan,
}),
input_key: ExprPlan::CompoundIdentifier {
alias: "Bar".to_owned(),
ident: "id".to_owned(),
},
right_key: ExprPlan::CompoundIdentifier {
alias: "Foo".to_owned(),
ident: "id".to_owned(),
},
right_filter: None,
}),
})),
group_by: Vec::new(),
aggregate_slots: Vec::new(),
},
expr: ExprPlan::Value(Value::Bool(true)),
}),
projection: ProjectionPlan::SelectItems(vec![SelectItemPlan::Wildcard]),
})));
assert_eq!(actual, expected);
}
#[test]
fn having() {
let actual = table("Bar")
.select()
.filter("id IS NULL")
.group_by("id, (a + name)")
.having("COUNT(id) > 10")
.offset(10);
let expected = "
SELECT * FROM Bar
WHERE id IS NULL
GROUP BY id, (a + name)
HAVING COUNT(id) > 10
OFFSET 10
";
test_query_builder(actual, expected);
let actual = table("Bar")
.select()
.filter("id IS NULL")
.group_by("id, (a + name)")
.having("COUNT(id) > 10")
.limit(10);
let expected = "
SELECT * FROM Bar
WHERE id IS NULL
GROUP BY id, (a + name)
HAVING COUNT(id) > 10
LIMIT 10
";
test_query_builder(actual, expected);
let actual = table("Bar")
.select()
.filter("id IS NULL")
.group_by("id, (a + name)")
.having("COUNT(id) > 10")
.project(vec!["id", "(a + name) AS b", "COUNT(id) AS c"]);
let expected = "
SELECT id, (a + name) AS b, COUNT(id) AS c
FROM Bar
WHERE id IS NULL
GROUP BY id, (a + name)
HAVING COUNT(id) > 10
";
test_query_builder(actual, expected);
let actual = table("Bar")
.select()
.filter("id IS NULL")
.group_by("id, (a + name)")
.having("COUNT(id) > 10");
let expected = "
SELECT * FROM Bar
WHERE id IS NULL
GROUP BY id, (a + name)
HAVING COUNT(id) > 10
";
test_query_builder(actual, expected);
let actual = table("Foo")
.select()
.group_by("a")
.having("a > 1")
.alias_as("Sub")
.select();
let expected = "SELECT * FROM (SELECT * FROM Foo GROUP BY a HAVING a > 1) Sub";
test_query_builder(actual, expected);
}
}