use {
super::{BuildProjectInputPlan, BuildProjectPlan, BuildSelect, DistinctNode},
crate::{
ast::{Projection, Select},
plan::{ProjectInputPlan, ProjectPlan, ProjectionPlan},
query_builder::{
ExprNode, FilterNode, GroupByNode, HavingNode, InnerHashJoinNode,
InnerJoinConditionNode, InnerNestedLoopJoinNode, LeftOuterHashJoinNode,
LeftOuterJoinConditionNode, LeftOuterNestedLoopJoinNode, LimitNode, OffsetNode,
OrderByExprList, QueryNode, SelectItemList, SelectNode, SelectOrderByNode, SourceNode,
},
result::Result,
},
};
#[derive(Clone, Debug)]
pub(super) enum PrevNode<'a> {
Select(SelectNode<'a>),
GroupBy(GroupByNode<'a>),
Having(HavingNode<'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>),
}
impl BuildProjectInputPlan for PrevNode<'_> {
fn build_project_input_plan(self) -> Result<ProjectInputPlan> {
match self {
Self::Select(node) => node.build_project_input_plan(),
Self::GroupBy(node) => node.build_project_input_plan(),
Self::Having(node) => node.build_project_input_plan(),
Self::InnerNestedLoop(node) => node.build_project_input_plan(),
Self::LeftOuterNestedLoop(node) => node.build_project_input_plan(),
Self::InnerHash(node) => node.build_project_input_plan(),
Self::LeftOuterHash(node) => node.build_project_input_plan(),
Self::InnerCondition(node) => node.build_project_input_plan(),
Self::LeftOuterCondition(node) => node.build_project_input_plan(),
Self::Filter(node) => node.build_project_input_plan(),
}
}
}
impl BuildSelect for PrevNode<'_> {
fn build_select(self) -> Result<Select> {
match self {
Self::Select(node) => node.build_select(),
Self::GroupBy(node) => node.build_select(),
Self::Having(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(),
}
}
}
impl<'a> From<SelectNode<'a>> for PrevNode<'a> {
fn from(node: SelectNode<'a>) -> Self {
PrevNode::Select(node)
}
}
impl<'a> From<GroupByNode<'a>> for PrevNode<'a> {
fn from(node: GroupByNode<'a>) -> Self {
PrevNode::GroupBy(node)
}
}
impl<'a> From<HavingNode<'a>> for PrevNode<'a> {
fn from(node: HavingNode<'a>) -> Self {
PrevNode::Having(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 {
PrevNode::Filter(node)
}
}
#[derive(Clone, Debug)]
pub struct ProjectNode<'a> {
prev_node: PrevNode<'a>,
select_items_list: Vec<SelectItemList<'a>>,
}
impl<'a> ProjectNode<'a> {
pub(super) fn new<N: Into<PrevNode<'a>>, T: Into<SelectItemList<'a>>>(
prev_node: N,
select_items: T,
) -> Self {
Self {
prev_node: prev_node.into(),
select_items_list: vec![select_items.into()],
}
}
#[must_use]
pub fn project<T: Into<SelectItemList<'a>>>(mut self, select_items: T) -> Self {
self.select_items_list.push(select_items.into());
self
}
pub fn alias_as(self, table_alias: &'a str) -> SourceNode<'a> {
QueryNode::ProjectNode(self).alias_as(table_alias)
}
pub fn order_by<T: Into<OrderByExprList<'a>>>(
self,
order_by_exprs: T,
) -> SelectOrderByNode<'a> {
SelectOrderByNode::new(self, order_by_exprs)
}
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 distinct(self) -> DistinctNode<'a> {
DistinctNode::new(self)
}
}
impl BuildProjectPlan for ProjectNode<'_> {
fn build_project_plan(self) -> Result<ProjectPlan> {
let input = self.prev_node.build_project_input_plan()?;
let projection = ProjectionPlan::SelectItems(
self.select_items_list
.into_iter()
.map(SelectItemList::build_select_items_plan)
.collect::<Result<Vec<Vec<_>>>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>(),
);
Ok(ProjectPlan { input, projection })
}
}
impl BuildSelect for ProjectNode<'_> {
fn build_select(self) -> Result<Select> {
let mut query = self.prev_node.build_select()?;
query.projection = Projection::SelectItems(
self.select_items_list
.into_iter()
.map(SelectItemList::build_select_items)
.collect::<Result<Vec<Vec<_>>>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>(),
);
Ok(query)
}
}
#[cfg(test)]
mod tests {
use {
crate::{
plan::{
HashJoinInputPlan, HashJoinPlan, InnerJoinInputPlan, InnerJoinPlan,
ProjectInputPlan, ProjectPlan, ProjectionPlan, QueryPlan, SourcePlan,
StatementPlan, TableAccessPlan, TableSourcePlan,
},
query_builder::{Build, SelectItemList, col, table, test_query_builder},
},
pretty_assertions::assert_eq,
};
#[test]
fn project() {
let actual = table("Good").select().project("id");
let expected = "SELECT id FROM Good";
test_query_builder(actual, expected);
let actual = table("Group").select().project("*, Group.*, name");
let expected = "SELECT *, Group.*, name FROM Group";
test_query_builder(actual, expected);
let actual = table("Foo")
.select()
.project(vec!["col1", "col2"])
.project("col3")
.project(vec!["col4".into(), col("col5")])
.project(col("col6"))
.project("col7 as hello");
let expected = "
SELECT
col1, col2, col3,
col4, col5, col6,
col7 as hello
FROM
Foo
";
test_query_builder(actual, expected);
let actual = table("Aliased").select().project("1 + 1 as col1, col2");
let expected = "SELECT 1 + 1 as col1, col2 FROM Aliased";
test_query_builder(actual, expected);
}
#[test]
fn prev_nodes() {
let actual = table("Foo").select().project("*");
let expected = "SELECT * FROM Foo";
test_query_builder(actual, expected);
let actual = table("Bar")
.select()
.group_by("city")
.project("city, COUNT(name) as num");
let expected = "
SELECT
city, COUNT(name) as num
FROM Bar
GROUP BY city
";
test_query_builder(actual, expected);
let actual = table("Cat")
.select()
.filter(r#"type = "cute""#)
.group_by("age")
.having("SUM(length) < 1000")
.project(col("age"))
.project("SUM(length)");
let expected = r#"
SELECT age, SUM(length)
FROM Cat
WHERE type = "cute"
GROUP BY age
HAVING SUM(length) < 1000;
"#;
test_query_builder(actual, expected);
let actual = table("Item").select().filter("price > 10").project("name");
let expected = "SELECT name FROM Item WHERE price > 10";
test_query_builder(actual, expected);
let actual = table("Item").select().join("Category").project("Item.name");
let expected = "SELECT Item.name FROM Item JOIN Category";
test_query_builder(actual, expected);
let actual = table("Item")
.select()
.join("Category")
.on("Item.category_id = Category.id")
.project("Item.name");
let expected = "SELECT Item.name FROM Item JOIN Category ON Item.category_id = Category.id";
test_query_builder(actual, expected);
let actual = table("Player")
.select()
.join("PlayerItem")
.hash_executor("PlayerItem.user_id", "Player.id")
.project("Player.name, PlayerItem.name")
.build();
let expected = {
let join = InnerJoinPlan {
input: InnerJoinInputPlan::Hash(HashJoinPlan {
input: HashJoinInputPlan::Source(SourcePlan::Table(TableSourcePlan {
name: "Player".to_owned(),
alias: None,
access: TableAccessPlan::FullScan,
})),
right: SourcePlan::Table(TableSourcePlan {
name: "PlayerItem".to_owned(),
alias: None,
access: TableAccessPlan::FullScan,
}),
input_key: col("Player.id").build_expr_plan().unwrap(),
right_key: col("PlayerItem.user_id").build_expr_plan().unwrap(),
right_filter: None,
}),
};
let project = ProjectPlan {
input: ProjectInputPlan::InnerJoin(Box::new(join)),
projection: ProjectionPlan::SelectItems(
SelectItemList::from("Player.name, PlayerItem.name")
.build_select_items_plan()
.unwrap(),
),
};
Ok(StatementPlan::Query(QueryPlan::Project(project)))
};
assert_eq!(actual, expected);
let actual = table("Foo").select().project("id").alias_as("Sub").select();
let expected = "SELECT * FROM (SELECT id FROM Foo) Sub";
test_query_builder(actual, expected);
}
}