use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{
Ident, parenthesized,
parse::{Parse, ParseStream},
};
use crate::{
clause::WithItem,
command::Command,
correlations::CorrelationId,
expr::Expr,
keyword,
part::{TableAlias, TablePath},
quote_option::QuoteOption,
scopes::ScopeId,
visitor::Visitor,
};
pub struct From {
pub _from: keyword::from,
pub chain: FromChain,
}
impl From {
pub fn accept<'a>(&'a self, visitor: &mut impl Visitor<'a>) {
self.chain.accept(visitor);
}
pub fn parse_optional(input: ParseStream) -> syn::Result<Option<Self>> {
Self::peek(input).then(|| input.parse()).transpose()
}
pub fn peek(input: ParseStream) -> bool {
input.peek(keyword::from)
}
}
impl Parse for From {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
_from: input.parse()?,
chain: input.parse()?,
})
}
}
impl ToTokens for From {
fn to_tokens(&self, tokens: &mut TokenStream) {
let item = &self.chain;
quote! { ::kosame::repr::clause::From::new(#item) }.to_tokens(tokens);
}
}
pub struct FromChain {
pub start: FromItem,
pub combinators: Vec<FromCombinator>,
}
impl FromChain {
pub fn accept<'a>(&'a self, visitor: &mut impl Visitor<'a>) {
self.start.accept(visitor);
for combinator in &self.combinators {
combinator.accept(visitor);
}
}
pub fn len(&self) -> usize {
self.combinators.len() + 1
}
pub fn nullables(&self) -> Vec<bool> {
let mut nullables = vec![false; self.len()];
{
let mut nullable_join_found = false;
for (index, combinator) in self.combinators.iter().enumerate() {
if let FromCombinator::Join { join_type, .. } = combinator {
match join_type {
JoinType::Left(..) => nullable_join_found = true,
JoinType::Full(..) => nullable_join_found = true,
_ => {}
}
}
nullables[index + 1] = nullables[index + 1] || nullable_join_found;
}
}
{
let mut nullable_join_found = false;
for (index, combinator) in self.combinators.iter().enumerate().rev() {
if let FromCombinator::Join { join_type, .. } = combinator {
match join_type {
JoinType::Right(..) => nullable_join_found = true,
JoinType::Full(..) => nullable_join_found = true,
_ => {}
}
}
nullables[index] = nullables[index] || nullable_join_found;
}
}
nullables
}
}
impl Parse for FromChain {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
start: input.parse()?,
combinators: input.call(FromCombinator::parse_many)?,
})
}
}
impl ToTokens for FromChain {
fn to_tokens(&self, tokens: &mut TokenStream) {
let start = &self.start;
let combinators = &self.combinators;
quote! {
::kosame::repr::clause::FromChain::new(#start, &[#(#combinators),*])
}
.to_tokens(tokens);
}
}
pub enum FromItem {
Table {
table_path: TablePath,
alias: Option<TableAlias>,
correlation_id: CorrelationId,
},
Subquery {
lateral_keyword: Option<keyword::lateral>,
paren_token: syn::token::Paren,
command: Box<Command>,
alias: Option<TableAlias>,
correlation_id: CorrelationId,
},
}
impl FromItem {
pub fn accept<'a>(&'a self, visitor: &mut impl Visitor<'a>) {
match self {
Self::Table { table_path, .. } => {
table_path.accept(visitor);
}
Self::Subquery { command, .. } => {
command.accept(visitor);
}
}
}
pub fn name(&self) -> Option<&Ident> {
match self {
Self::Table {
table_path, alias, ..
} => Some(alias.as_ref().map(|alias| &alias.name).unwrap_or_else(|| {
&table_path
.as_path()
.segments
.last()
.expect("path cannot be empty")
.ident
})),
Self::Subquery { alias, .. } => alias.as_ref().map(|alias| &alias.name),
}
}
pub fn correlation_id(&self) -> CorrelationId {
match self {
Self::Table { correlation_id, .. } => *correlation_id,
Self::Subquery { correlation_id, .. } => *correlation_id,
}
}
pub fn columns<'a>(&'a self, with_item: Option<&'a WithItem>) -> Vec<&'a Ident> {
match self {
Self::Table { alias, .. } => match with_item {
Some(with_item) => with_item.columns(),
None => match alias {
Some(
TableAlias {
columns: Some(columns),
..
},
..,
) => columns.columns.iter().collect(),
_ => Vec::new(),
},
},
Self::Subquery { command, .. } => command
.fields()
.into_iter()
.flat_map(|fields| fields.columns())
.collect(),
}
}
}
impl Parse for FromItem {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lateral_keyword = input
.peek(keyword::lateral)
.then(|| input.parse())
.transpose()?;
let lookahead = input.lookahead1();
if lookahead.peek(syn::token::Paren) {
let content;
Ok(Self::Subquery {
lateral_keyword,
paren_token: parenthesized!(content in input),
command: content.parse()?,
alias: input.call(TableAlias::parse_optional)?,
correlation_id: CorrelationId::new(),
})
} else if lookahead.peek(Ident) {
Ok(Self::Table {
table_path: input.parse()?,
alias: input.call(TableAlias::parse_optional)?,
correlation_id: CorrelationId::new(),
})
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for FromItem {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Table {
table_path, alias, ..
} => {
let table = alias.as_ref().map(|alias| &alias.name).unwrap_or(
&table_path
.as_path()
.segments
.last()
.expect("paths cannot be empty")
.ident,
);
let alias = QuoteOption::from(alias);
let scope_id = ScopeId::of_scope();
quote! {
::kosame::repr::clause::FromItem::Table {
table: scopes::#scope_id::tables::#table::TABLE_NAME,
alias: #alias,
}
}
}
Self::Subquery {
lateral_keyword: _lateral_keyword,
command,
alias,
..
} => {
let lateral = _lateral_keyword.is_some();
let alias = QuoteOption::from(alias);
quote! {
::kosame::repr::clause::FromItem::Subquery {
lateral: #lateral,
command: &#command,
alias: #alias,
}
}
}
}
.to_tokens(tokens);
}
}
#[allow(unused)]
pub enum JoinType {
Inner(keyword::inner, keyword::join),
Left(keyword::left, keyword::join),
Right(keyword::right, keyword::join),
Full(keyword::full, keyword::join),
}
impl JoinType {
fn peek(input: ParseStream) -> bool {
macro_rules! check {
($kw:expr) => {
if input.peek($kw) {
return true;
}
};
}
check!(keyword::inner);
check!(keyword::left);
check!(keyword::right);
check!(keyword::full);
false
}
}
impl Parse for JoinType {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(keyword::inner) {
Ok(Self::Inner(
input.call(keyword::inner::parse_autocomplete)?,
input.call(keyword::join::parse_autocomplete)?,
))
} else if lookahead.peek(keyword::left) {
Ok(Self::Left(
input.call(keyword::left::parse_autocomplete)?,
input.call(keyword::join::parse_autocomplete)?,
))
} else if lookahead.peek(keyword::right) {
Ok(Self::Right(
input.call(keyword::right::parse_autocomplete)?,
input.call(keyword::join::parse_autocomplete)?,
))
} else if lookahead.peek(keyword::full) {
Ok(Self::Full(
input.call(keyword::full::parse_autocomplete)?,
input.call(keyword::join::parse_autocomplete)?,
))
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for JoinType {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Inner(..) => quote! { ::kosame::repr::clause::JoinType::Inner },
Self::Left(..) => quote! { ::kosame::repr::clause::JoinType::Left },
Self::Right(..) => quote! { ::kosame::repr::clause::JoinType::Right },
Self::Full(..) => quote! { ::kosame::repr::clause::JoinType::Full },
}
.to_tokens(tokens);
}
}
pub struct On {
pub _on_keyword: keyword::on,
pub expr: Expr,
}
impl Parse for On {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
_on_keyword: input.call(keyword::on::parse_autocomplete)?,
expr: input.parse()?,
})
}
}
pub enum FromCombinator {
Join {
join_type: JoinType,
right: Box<FromItem>,
on: On,
},
NaturalJoin {
_natural_keyword: keyword::natural,
join_type: JoinType,
right: Box<FromItem>,
},
CrossJoin {
_cross_keyword: keyword::cross,
_join_keyword: keyword::join,
right: Box<FromItem>,
},
}
impl FromCombinator {
pub fn accept<'a>(&'a self, visitor: &mut impl Visitor<'a>) {
match self {
Self::Join { right, on, .. } => {
right.accept(visitor);
on.expr.accept(visitor);
}
Self::NaturalJoin { right, .. } => {
right.accept(visitor);
}
Self::CrossJoin { right, .. } => {
right.accept(visitor);
}
}
}
pub fn peek(input: ParseStream) -> bool {
JoinType::peek(input) || input.peek(keyword::natural) || input.peek(keyword::cross)
}
fn parse_many(input: ParseStream) -> syn::Result<Vec<FromCombinator>> {
let mut result = Vec::new();
while Self::peek(input) {
result.push(input.parse()?);
}
Ok(result)
}
pub fn right(&self) -> &FromItem {
match self {
Self::Join { right, .. } => right,
Self::NaturalJoin { right, .. } => right,
Self::CrossJoin { right, .. } => right,
}
}
}
impl Parse for FromCombinator {
fn parse(input: ParseStream) -> syn::Result<Self> {
if JoinType::peek(input) {
Ok(Self::Join {
join_type: input.parse()?,
right: Box::new(input.parse()?),
on: input.parse()?,
})
} else if input.peek(keyword::natural) {
Ok(Self::NaturalJoin {
_natural_keyword: input.call(keyword::natural::parse_autocomplete)?,
join_type: input.parse()?,
right: Box::new(input.parse()?),
})
} else if input.peek(keyword::cross) {
Ok(Self::CrossJoin {
_cross_keyword: input.call(keyword::cross::parse_autocomplete)?,
_join_keyword: input.call(keyword::join::parse_autocomplete)?,
right: Box::new(input.parse()?),
})
} else {
Err(syn::Error::new(input.span(), "expected join"))
}
}
}
impl ToTokens for FromCombinator {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Join {
join_type,
right,
on,
..
} => {
let on = &on.expr;
quote! {
::kosame::repr::clause::FromCombinator::Join {
join_type: #join_type,
right: #right,
on: #on,
}
}
}
Self::NaturalJoin {
join_type, right, ..
} => {
quote! {
::kosame::repr::clause::FromCombinator::NaturalJoin {
join_type: #join_type,
right: #right,
}
}
}
Self::CrossJoin { right, .. } => {
quote! {
::kosame::repr::clause::FromCombinator::CrossJoin {
right: #right,
}
}
}
}
.to_tokens(tokens);
}
}
pub struct FromIter<'a> {
chain: &'a FromChain,
index: i32,
}
impl<'a> Iterator for FromIter<'a> {
type Item = &'a FromItem;
fn next(&mut self) -> Option<Self::Item> {
let item = match self.index {
-1 => &self.chain.start,
_ => self.chain.combinators.get(self.index as usize)?.right(),
};
self.index += 1;
Some(item)
}
}
impl<'a> IntoIterator for &'a FromChain {
type IntoIter = FromIter<'a>;
type Item = &'a FromItem;
fn into_iter(self) -> Self::IntoIter {
FromIter {
index: -1,
chain: self,
}
}
}