orql 0.1.0

A toy SQL parser for a subset of the Oracle dialect.
Documentation
use super::{Expr, ExprList, Node, Query};

/// A conditional "expression", i.e. the description whether a certain
/// condition holds or not.
///
/// Unlike [expressions](Expr), conditions do _not_ represent a generic
/// value. Conditions are allowed only in certain parts of SQL statements,
/// such as `WHERE`, `JOIN`, `HAVING`, or `CASE` condition clauses for
/// example.
#[derive(Debug)]
pub enum Condition<'s, ID> {
    /// e.g. `(<condition>)`
    Nested(Node<Box<Condition<'s, ID>>, ID>),
    /// e.g. `EXISTS (<sub-query>)`
    Exists(ExistsCondition<'s, ID>),
    /// e.g. `<expr> [NOT] BETWEEN <expr> AND <expr>`
    Between(Box<BetweenCondition<'s, ID>>),
    /// e.g. `<expr> [NOT] IN (<expr>, <expr>)`
    In(Box<InCondition<'s, ID>>),
    /// e.g. `1 = 1`
    Compare(CompareCondition<'s, ID>),
    /// e.g. `<condition> AND <condition>`
    And(AndCondition<'s, ID>),
    /// e.g. `<condition> OR <condition>`
    Or(OrCondition<'s, ID>),
    /// e.g. `NOT <condition>`
    Not(NotCondition<'s, ID>),
    /// e.g. `<expr> LIKE <expr> [ESCAPE <expr>]`
    Like(Box<LikeCondition<'s, ID>>),
    /// e.g. `REGEXP_LIKE (name, '(^[aeiou])', 'i')`
    RegexpLike(Box<RegexpLikeCondition<'s, ID>>),
    /// e.g. `<expr> IS [NOT] NULL`
    IsNull(IsNullCondition<'s, ID>),
    /// Condition on a floating point number
    ///
    /// e.g. `<expr> IS [NOT] NAN`
    IsFloat(IsFloatCondition<'s, ID>),
}

#[derive(Debug)]
pub struct ExistsCondition<'s, ID> {
    /// the `EXISTS` keyword
    pub exists_token: Node<(), ID>,
    /// the subquery, e.g. `(SELECT ...)`
    pub query: Node<Box<Query<'s, ID>>, ID>,
}

#[derive(Debug)]
pub struct BetweenCondition<'s, ID> {
    /// the (value) expression between tested
    pub expr: Expr<'s, ID>,
    /// the `NOT` keyword
    ///
    /// if present the condition is negated, meaning `expr` does is not
    /// contained in the specified range
    pub not_token: Option<Node<(), ID>>,
    /// the `BETWEEN` token
    pub between_token: Node<(), ID>,
    /// the lower bound of the "between" range
    pub range_from: Expr<'s, ID>,
    /// the `AND` token
    pub and_token: Node<(), ID>,
    /// the upper bound of the "between" range
    pub range_upto: Expr<'s, ID>,
}

/// An `AND` combination of two conditions. See [Condition::And].
#[derive(Debug)]
pub struct AndCondition<'s, ID> {
    pub left: Box<Condition<'s, ID>>,
    pub and_token: Node<(), ID>,
    pub right: Box<Condition<'s, ID>>,
}

#[derive(Debug)]
pub struct OrCondition<'s, ID> {
    pub left: Box<Condition<'s, ID>>,
    pub or_token: Node<(), ID>,
    pub right: Box<Condition<'s, ID>>,
}

/// A `<expr> IS [NOT] NULL` condition
#[derive(Debug)]
pub struct IsNullCondition<'s, ID> {
    /// the expression being tested
    pub expr: Box<Expr<'s, ID>>,

    /// the `IS` keyword
    pub is_token: Node<(), ID>,

    /// the `NOT` keyword
    ///
    /// if present, tests `expr` for being not null, otherwise for being null
    pub not_token: Option<Node<(), ID>>,

    /// the `NULL` keyword
    pub null_token: Node<(), ID>,
}

/// A `<expr> is [NOT] NAN | INFINITE` condition
#[derive(Debug)]
pub struct IsFloatCondition<'s, ID> {
    /// the expression being tested against
    pub expr: Box<Expr<'s, ID>>,

    /// the `IS` keyword
    pub is_token: Node<(), ID>,

    /// the `NOT` keyword
    ///
    /// if present the condition is negated, meaning `expr` does not match the
    /// specified `float_type`
    pub not_token: Option<Node<(), ID>>,

    /// the float type to check `expr` against
    pub float_type: Node<FloatType, ID>,
}

/// Floating point number condition type of a [IsFloatCondition]
#[derive(Debug)]
pub enum FloatType {
    /// e.g. `IS NAN`
    Nan,
    /// e.g. `IS INFINITE`
    Infinite,
}

/// Membership test, e.g. `<expr> [NOT] IN (<expr>, <expr>)`
#[derive(Debug)]
pub struct InCondition<'s, ID> {
    /// the value expression being tested for containment in `list`
    ///
    /// Note: [CompareExpr::Lists] is actually not valid on the left-hand-side
    pub expr: CompareExpr<'s, ID>,

    /// the `NOT` keyword
    ///
    /// if present the condition is negated, meaning `value` is not to be
    /// contained in the target values
    pub not_token: Option<Node<(), ID>>,

    /// the `IN` token
    pub in_token: Node<(), ID>,

    /// the list of values (or a subquery) to test `expr` for containment in
    pub values: CompareExpr<'s, ID>,
}

#[derive(Debug)]
pub struct LikeCondition<'s, ID> {
    /// the source expression which `pattern` is to be matched against
    pub source: Expr<'s, ID>,
    /// the `NOT` keyword
    ///
    /// if present it negates the matching, i.e. `pattern` must _not_ match `source`
    pub not_token: Option<Node<(), ID>>,
    /// the `LIKE` keyword (or one of its variants)
    pub like_token: Node<LikeVariant, ID>,
    /// the pattern to search in `source`; typically a string literal but can
    /// be any expression evaluating to a string
    pub pattern: Expr<'s, ID>,
    /// the optional `ESCAPE` character
    pub escape: Option<LikeEscape<'s, ID>>,
}

/// A regular expression matching condition; `REGEXP_LIKE(<source>, <pattern>, <opts>)`
///
/// [Specification](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Pattern-matching-Conditions.html#GUID-D2124F3A-C6E4-4CCA-A40E-2FFCABFD8E19)
#[derive(Debug)]
pub struct RegexpLikeCondition<'s, ID> {
    /// the `REGEXP_LIKE` token
    pub regexp_like_token: Node<(), ID>,

    /// the regexp match parameters / details,
    /// e.g. `(<source>, <pattern>, <opts>)` surrounded by parens
    pub params: Node<RegexpLikeParams<'s, ID>, ID>,
}

/// The definition of parameters for ["REGEXP_LIKE"](RegexpLikeCondition) matching.
#[derive(Debug)]
pub struct RegexpLikeParams<'s, ID> {
    /// the expression providing the source string to match a `pattern`
    /// against
    pub source: Expr<'s, ID>,

    /// the pattern to match against `source`, typically a string literal but
    /// can be any expression evaulating into a string
    pub pattern: Expr<'s, ID>,

    /// options modifying the matching behaviour; typically a string
    /// literal but can be any expression evaluating into a string.
    ///
    /// Valid to include one or more of the `i`, `c`, `n`, `m`, `x`
    /// characters.  (This is, however, _not_ being enforced by the parser.)
    pub options: Option<Expr<'s, ID>>,
}

#[derive(Debug)]
pub struct LikeEscape<'s, ID> {
    /// the `ESCAPE` token
    pub escape_token: Node<(), ID>,
    /// the escape character itself; typically a literal string of length one,
    /// but can be any expression evaluating to a single character
    pub escape_char: Expr<'s, ID>,
}

/// "… `LIKE` calculates strings using characters as defined by the input character set. `LIKEC` uses Unicode complete characters. `LIKE2` uses UCS2 code points. `LIKE4` uses UCS4 code points. …" [source](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Pattern-matching-Conditions.html)
#[derive(Debug)]
pub enum LikeVariant {
    /// `LIKE`
    Like,
    /// `LIKE2`
    Like2,
    /// `LIKE4`
    Like4,
    /// `LIKEC`
    LikeC,
}

// XXX refine this type into a more detailed representation reflecting
// the different (valid) paths: https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comparison-Conditions.html#GUID-2590303E-81FE-4758-A971-1EE8B798951F
#[derive(Debug)]
pub struct CompareCondition<'s, ID> {
    /// The left-hand-side expression of the operation
    pub left: Box<CompareExpr<'s, ID>>,
    /// the operation
    pub op: CompareOp<ID>,
    /// The right-hand-side expression of the operation
    pub right: Box<CompareExpr<'s, ID>>,
}

#[derive(Debug)]
pub enum CompareExpr<'s, ID> {
    /// e.g. `(1 + 2)`
    Expr(Expr<'s, ID>),
    /// e.g. `(1, 2)`
    List(ExprList<'s, ID>),
    /// e.g. `((1, 2))`, `((1, 2), (3, 4))`
    Lists(Node<Vec<ExprList<'s, ID>>, ID>),
}

/// A inverted condition, i.e. `NOT <condition>`
#[derive(Debug)]
pub struct NotCondition<'s, ID> {
    /// the NOT keyword
    pub not_token: Node<(), ID>,
    /// the negated condition
    pub condition: Box<Condition<'s, ID>>,
}

impl<'s, ID> From<Expr<'s, ID>> for CompareExpr<'s, ID> {
    fn from(value: Expr<'s, ID>) -> Self {
        Self::Expr(value)
    }
}

#[derive(Debug)]
pub struct CompareOp<ID> {
    /// the comparison operator
    pub kind: Node<CompareKind, ID>,
    /// a quantifier for a "group" or "membership" comparison
    pub quantifier: Option<Node<CompareQuantifier, ID>>,
}

#[derive(Debug, Clone, Copy)]
pub enum CompareKind {
    /// `a == b`
    Eq,
    /// `a != b`
    NotEq(NotEqSymbol),
    /// `a < b`
    Lt,
    /// `a <= b`
    LtEq,
    /// `a > b`
    Gt,
    /// `a >= b`
    GtEq,
}

#[derive(Debug, Clone, Copy)]
pub enum NotEqSymbol {
    /// `!=`
    Logical,
    /// `<>`
    Diamond,
    /// `^=`
    Bitwise,
}

#[derive(Debug, Clone, Copy)]
pub enum CompareQuantifier {
    /// e.g. `a == ALL (b, c)`
    All,
    /// e.g. `a == ANY (b, c)`
    Any,
    /// e.g. `a == SOME (b, c)`
    Some,
}