use crate::{ExprRef, NodeRef, Slice, StrRef};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
Get {
catalog: StrRef,
schema: StrRef,
table: StrRef,
alias: StrRef,
index: u32,
columns: Slice,
},
Dummy,
Values {
index: u32,
columns: Slice,
rows: Slice,
},
TableFunction {
index: u32,
function: StrRef,
args: Slice,
columns: Slice,
},
Filter {
input: NodeRef,
predicate: ExprRef,
},
Project {
input: NodeRef,
index: u32,
exprs: Slice,
names: Slice,
},
Aggregate {
input: NodeRef,
index: u32,
groups: Slice,
aggregates: Slice,
},
Sort {
input: NodeRef,
keys: Slice,
},
Limit {
input: NodeRef,
count: Option<u64>,
offset: u64,
},
Distinct {
input: NodeRef,
on: Slice,
},
Join {
left: NodeRef,
right: NodeRef,
kind: JoinKind,
conditions: Slice,
},
CrossProduct {
left: NodeRef,
right: NodeRef,
},
SetOp {
left: NodeRef,
right: NodeRef,
kind: SetOpKind,
all: bool,
index: u32,
},
}
impl Node {
#[must_use]
pub fn keyword(&self) -> &'static str {
match self {
Self::Get { .. } => "Get",
Self::Dummy => "Dummy",
Self::Values { .. } => "Values",
Self::TableFunction { .. } => "TableFunction",
Self::Filter { .. } => "Filter",
Self::Project { .. } => "Project",
Self::Aggregate { .. } => "Aggregate",
Self::Sort { .. } => "Sort",
Self::Limit { .. } => "Limit",
Self::Distinct { .. } => "Distinct",
Self::Join { .. } => "Join",
Self::CrossProduct { .. } => "CrossProduct",
Self::SetOp { .. } => "SetOp",
}
}
#[must_use]
pub fn children(&self) -> [Option<NodeRef>; 2] {
match *self {
Self::Get { .. } | Self::Dummy | Self::Values { .. } | Self::TableFunction { .. } => {
[None, None]
}
Self::Filter { input, .. }
| Self::Project { input, .. }
| Self::Aggregate { input, .. }
| Self::Sort { input, .. }
| Self::Limit { input, .. }
| Self::Distinct { input, .. } => [Some(input), None],
Self::Join { left, right, .. }
| Self::CrossProduct { left, right }
| Self::SetOp { left, right, .. } => [Some(left), Some(right)],
}
}
#[must_use]
pub fn arity(&self) -> usize {
self.children().into_iter().flatten().count()
}
#[must_use]
pub fn table_index(&self) -> Option<u32> {
match *self {
Self::Get { index, .. }
| Self::Values { index, .. }
| Self::TableFunction { index, .. }
| Self::Project { index, .. }
| Self::Aggregate { index, .. }
| Self::SetOp { index, .. } => Some(index),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JoinKind {
Inner,
Left,
Right,
Full,
Semi,
Anti,
Single,
Positional,
}
impl JoinKind {
#[must_use]
pub fn keyword(self) -> &'static str {
match self {
Self::Inner => "INNER",
Self::Left => "LEFT",
Self::Right => "RIGHT",
Self::Full => "FULL",
Self::Semi => "SEMI",
Self::Anti => "ANTI",
Self::Single => "SINGLE",
Self::Positional => "POSITIONAL",
}
}
pub(crate) const ALL: [Self; 8] = [
Self::Inner,
Self::Left,
Self::Right,
Self::Full,
Self::Semi,
Self::Anti,
Self::Single,
Self::Positional,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SetOpKind {
Union,
Except,
Intersect,
}
impl SetOpKind {
#[must_use]
pub fn keyword(self) -> &'static str {
match self {
Self::Union => "UNION",
Self::Except => "EXCEPT",
Self::Intersect => "INTERSECT",
}
}
pub(crate) const ALL: [Self; 3] = [Self::Union, Self::Except, Self::Intersect];
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Slice;
fn one_of_each() -> Vec<Node> {
vec![
Node::Get {
catalog: 0,
schema: 0,
table: 0,
alias: 0,
index: 0,
columns: Slice::EMPTY,
},
Node::Dummy,
Node::Values { index: 0, columns: Slice::EMPTY, rows: Slice::EMPTY },
Node::TableFunction {
index: 0,
function: 0,
args: Slice::EMPTY,
columns: Slice::EMPTY,
},
Node::Filter { input: 0, predicate: 0 },
Node::Project { input: 0, index: 0, exprs: Slice::EMPTY, names: Slice::EMPTY },
Node::Aggregate { input: 0, index: 0, groups: Slice::EMPTY, aggregates: Slice::EMPTY },
Node::Sort { input: 0, keys: Slice::EMPTY },
Node::Limit { input: 0, count: None, offset: 0 },
Node::Distinct { input: 0, on: Slice::EMPTY },
Node::Join { left: 0, right: 1, kind: JoinKind::Inner, conditions: Slice::EMPTY },
Node::CrossProduct { left: 0, right: 1 },
Node::SetOp { left: 0, right: 1, kind: SetOpKind::Union, all: true, index: 0 },
]
}
#[test]
fn every_operator_has_its_own_keyword() {
let mut keywords: Vec<&str> = one_of_each().iter().map(Node::keyword).collect();
let count = keywords.len();
keywords.sort_unstable();
keywords.dedup();
assert_eq!(keywords.len(), count, "two operators print the same keyword");
}
#[test]
fn arity_agrees_with_the_child_slots() {
for node in one_of_each() {
let counted = node.children().into_iter().flatten().count();
assert_eq!(node.arity(), counted, "{} disagrees with itself", node.keyword());
}
}
#[test]
fn the_child_slots_are_filled_from_the_front() {
for node in one_of_each() {
let slots = node.children();
assert!(
!(slots[0].is_none() && slots[1].is_some()),
"{} has a right input and no left one",
node.keyword()
);
}
}
#[test]
fn only_the_operators_that_introduce_columns_have_a_table_index() {
for node in one_of_each() {
let expected = matches!(
node,
Node::Get { .. }
| Node::Values { .. }
| Node::TableFunction { .. }
| Node::Project { .. }
| Node::Aggregate { .. }
| Node::SetOp { .. }
);
assert_eq!(
node.table_index().is_some(),
expected,
"{} is on the wrong side of the table index rule",
node.keyword()
);
}
}
#[test]
fn every_join_kind_and_set_operation_is_in_the_list_the_reader_searches() {
assert_eq!(JoinKind::ALL.len(), 8);
assert_eq!(SetOpKind::ALL.len(), 3);
let mut names: Vec<&str> = JoinKind::ALL.iter().map(|k| k.keyword()).collect();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), JoinKind::ALL.len(), "two join kinds print the same keyword");
}
}