orql 0.1.0

A toy SQL parser for a subset of the Oracle dialect.
Documentation
use super::{MetaTracker, ParserInner, Result};
use crate::{
    ast::{
        FetchAmount, FetchAmountSpec, FetchFirstKeyword, Node, QueryTableAlias, RowKeyword,
        RowLimit, RowLimitFetch, RowLimitOffset,
    },
    parser::expression::ExprParser,
    scanner::{Keyword, Reserved, Token, TokenType},
};

impl<'s, M> ParserInner<'s, M>
where
    M: MetaTracker<'s>,
{
    pub(super) fn parse_row_limit<'a>(
        &mut self,
        mut steal_alias_if_needed: Option<&'a mut QueryTableAlias<'s, M::NodeId>>,
    ) -> Result<RowLimit<'s, M::NodeId>> {
        let offset = if let Some(offset_token) =
            self.next_token_if_reserved_offset(&mut steal_alias_if_needed)?
        {
            let offset_value = self.parse_expr()?;
            let row_token = {
                let (kw, loc) = expect_reserved!(|t = self.next_token()| "the ROW or ROWS keyword" match {
                    Reserved::ROW => (RowKeyword::Row, t.loc),
                    Reserved::ROWS => (RowKeyword::Rows, t.loc),
                });
                Node(kw, self.meta_tracker.on_node_start(loc))
            };

            // ~ we have an 'offset' clause, the possible 'limit' following
            // next should not see the alias as the last token
            steal_alias_if_needed = None;
            Some(RowLimitOffset {
                offset_token,
                offset_value,
                row_token,
            })
        } else {
            None
        };
        let fetch = if let Some(fetch_token) =
            self.next_token_if_reserved_fetch(&mut steal_alias_if_needed)?
        {
            let first_token = {
                let (kw, loc) = expect_reserved!(|t = self.next_token()| "the FIRST or NEXT keyword" match {
                    Reserved::FIRST => (FetchFirstKeyword::First, t.loc),
                    Reserved::NEXT => (FetchFirstKeyword::Next, t.loc),
                });
                Node(kw, self.meta_tracker.on_node_start(loc))
            };
            let fetch_amount = {
                let amount = self.parse_expr()?;
                if let Some(Token {
                    ttype: TokenType::Identifier(_, Some(Reserved::PERCENT)),
                    loc,
                }) = self.peek_token()?
                {
                    let loc = *loc;
                    self.consume_token()?;
                    FetchAmount::Percent {
                        amount,
                        percent_token: Node((), self.meta_tracker.on_node_start(loc)),
                    }
                } else {
                    FetchAmount::Count(amount)
                }
            };
            let row_token = {
                let (kw, loc) = expect_reserved!(|t = self.next_token()| "the ROW or ROWS keyword" match {
                    Reserved::ROW => (RowKeyword::Row, t.loc),
                    Reserved::ROWS => (RowKeyword::Rows, t.loc),
                });
                Node(kw, self.meta_tracker.on_node_start(loc))
            };
            let fetch_amount_spec = expect_token!(|t = self.next_token()| "the ONLY or WITH TIES keywords" match {
                TokenType::Identifier(_, Some(Reserved::ONLY)) => FetchAmountSpec::Only { only_token: Node((), self.meta_tracker.on_node_start(t.loc) ) },
                TokenType::Keyword(Keyword::WITH) => {
                    let with_token = Node((), self.meta_tracker.on_node_start(t.loc));
                    let ties_token = expect_reserved!(|t = self.next_token()| "the TIES keyword" match {
                        Reserved::TIES => Node((), self.meta_tracker.on_node_start(t.loc)),
                    });
                    FetchAmountSpec::WithTies { with_token, ties_token }
                }
            });
            Some(RowLimitFetch {
                fetch_token,
                first_token,
                row_token,
                fetch_amount,
                fetch_amount_spec,
            })
        } else {
            None
        };
        Ok(RowLimit { offset, fetch })
    }

    /// Retrieves the next token (or steals it from the given alias) if and
    /// only if it matches `reserved`.
    fn next_token_if_reserved_offset<'a>(
        &mut self,
        steal_alias_if_needed: &mut Option<&'a mut QueryTableAlias<'s, M::NodeId>>,
    ) -> Result<Option<Node<(), M::NodeId>>> {
        // ~ steal the alias only if the next token is an expression
        let try_stealing_alias = if let Some(t) = self.peek_token()? {
            match &t.ttype {
                TokenType::Identifier(_, Some(Reserved::OFFSET)) => {
                    let loc = t.loc;
                    self.consume_token()?;
                    return Ok(Some(Node((), self.meta_tracker.on_node_start(loc))));
                }
                _ => ExprParser::<M>::is_start_token(t),
            }
        } else {
            false
        };
        if try_stealing_alias
            && let Some(steal_alias_if_needed) = steal_alias_if_needed
            && let Some(Node(ident, ident_id)) = steal_alias_if_needed
            && Reserved::OFFSET.matches(ident)
        {
            let node_id = *ident_id;
            steal_alias_if_needed.take();
            return Ok(Some(Node((), node_id)));
        }
        Ok(None)
    }

    fn next_token_if_reserved_fetch<'a>(
        &mut self,
        steal_alias_if_needed: &mut Option<&'a mut QueryTableAlias<'s, M::NodeId>>,
    ) -> Result<Option<Node<(), M::NodeId>>> {
        // ~ steal the alias only if the next token is an expression
        let try_stealing_alias = if let Some(t) = self.peek_token()? {
            match &t.ttype {
                TokenType::Identifier(_, Some(Reserved::FETCH)) => {
                    let loc = t.loc;
                    self.consume_token()?;
                    return Ok(Some(Node((), self.meta_tracker.on_node_start(loc))));
                }
                TokenType::Identifier(_, Some(Reserved::FIRST | Reserved::NEXT)) => true,
                _ => false,
            }
        } else {
            false
        };
        if try_stealing_alias
            && let Some(steal_alias_if_needed) = steal_alias_if_needed
            && let Some(Node(ident, ident_id)) = steal_alias_if_needed
            && Reserved::FETCH.matches(ident)
        {
            let node_id = *ident_id;
            steal_alias_if_needed.take();
            return Ok(Some(Node((), node_id)));
        }
        Ok(None)
    }
}