//! Based on
//! https://github.com/pest-parser/pest/blob/master/grammars/src/grammars/sql.pest.
//! (Original is dual-licensed under MIT/Apache-2.0.)
//!
//! PostgreSQL departs extensively from the SQLite flavored dialect captured in
//! the original grammar. For example, rules for identifiers/object names
//! differ, as do keywords, built-in types, and syntax for specifying function
//! arguments, type modifiers, CTEs, and so on.
//!
//! This grammar covers a larger subset of the Postgres SQL dialect, but it is a
//! work in progress and is far from complete. It should only be used to parse
//! input that is "PostgreSQL-esque", not input that expects spec compliance.

Command = _{ SOI ~ (Query | ExplainQuery | DDL | ACL) ~ EOF }

ACL = _{ DropRole | DropUser | CreateRole | CreateUser | AlterUser | GrantPrivilege | RevokePrivilege }
    CreateUser = {
        ^"create" ~ ^"user" ~ Identifier ~ (^"with")? ~ ^"password" ~ SingleQuotedString ~
        AuthMethod?
    }
    AlterUser = {
        ^"alter" ~ ^"user" ~ Identifier ~ (^"with")? ~ AlterOption
    }
        AlterOption = _{ AlterLogin | AlterNoLogin | AlterPassword }
            AlterLogin = { ^"login" }
            AlterNoLogin = { ^"nologin" }
            AlterPassword = { ^"password" ~ SingleQuotedString ~ AuthMethod? }
	AuthMethod = { ^"using" ~ (ChapSha1 | Md5 | Ldap) }
            ChapSha1 = { ^"chap-sha1" }
            Md5 = { ^"md5" }
            Ldap = { ^"ldap" }
    DropUser = { ^"drop" ~ ^"user" ~ Identifier }
    CreateRole = { ^"create" ~ ^"role" ~ Identifier }
    DropRole = { ^"drop" ~ ^"role" ~ Identifier }
    GrantPrivilege = { ^"grant" ~ PrivBlock ~ ^"to" ~ Identifier }
    RevokePrivilege = { ^"revoke" ~ PrivBlock ~ ^"from" ~ Identifier }
        PrivBlock = _{ PrivBlockPrivilege | PrivBlockRolePass }
            PrivBlockPrivilege = {Privilege ~ (PrivBlockUser | PrivBlockSpecificUser | PrivBlockRole
                                                | PrivBlockSpecificRole | PrivBlockTable | PrivBlockSpecificTable)}
            PrivBlockUser = { ^"user" }
            PrivBlockSpecificUser = { ^"on" ~ ^"user" ~ Identifier }
            PrivBlockRole = { ^"role" }
            PrivBlockSpecificRole = { ^"on" ~ ^"role" ~ Identifier }
            PrivBlockTable = { ^"table" }
            PrivBlockSpecificTable = { ^"on" ~ ^"table" ~ Identifier }
            PrivBlockRolePass = { Identifier }
        Privilege = _{ PrivilegeRead | PrivilegeWrite | PrivilegeExecute |
                      PrivilegeCreate | PrivilegeAlter | PrivilegeDrop |
                      PrivilegeSession | PrivilegeUsage }
            PrivilegeAlter = { ^"alter" }
            PrivilegeCreate = { ^"create" }
            PrivilegeDrop = { ^"drop" }
            PrivilegeExecute = { ^"execute" }
            PrivilegeRead = { ^"read" }
            PrivilegeSession = { ^"session" }
            PrivilegeUsage = { ^"usage" }
            PrivilegeWrite = { ^"write" }

DDL = _{ CreateTable | DropTable | CreateProc }
    CreateTable = {
        ^"create" ~ ^"table" ~ Identifier ~
        "(" ~ Columns ~ "," ~ PrimaryKey ~ ")" ~
        Distribution
    }
        Columns = { ColumnDef ~ ("," ~ ColumnDef)* }
            ColumnDef = { Identifier ~ TypeCast ~ ColumnDefIsNull? }
            ColumnDefIsNull = { NotFlag? ~ ^"null" }
        PrimaryKey = {
            ^"primary" ~ ^"key" ~
            "(" ~ Identifier ~ ("," ~ Identifier)* ~ ")"
        }
        Distribution = { ^"distributed" ~ (Global | Sharding) }
        Global = { ^"globally" }
        Sharding = { ^"by" ~ "(" ~ Identifier ~ ("," ~ Identifier)* ~ ")"}
    DropTable = { ^"drop" ~ ^"table" ~ Identifier }

    CreateProc = {
        ^"create" ~ ^"procedure" ~ Identifier ~
        "(" ~ ProcParams? ~ ")" ~ (^"language" ~ ProcLanguage)? ~
        ((^"as" ~ "$$" ~ ProcBody ~ "$$") | (^"begin" ~ "atomic" ~ ProcBody ~ "end"))
    }
        ProcParams = { ProcParamDef ~ ("," ~ ProcParamDef)* }
            ProcParamDef = { TypeCast }
        ProcLanguage = { SQL }
            SQL = { ^"sql" }
        ProcBody = { (Insert | Update | Delete) }

ExplainQuery = _{ Explain }
    Explain = { ^"explain" ~ Query }

Query = { (SelectWithOptionalContinuation | Values | Insert | Update | Delete) }
    SelectWithOptionalContinuation = { Select ~ (ExceptContinuation | UnionAllContinuation)? }
        ExceptContinuation   = { ((^"except" ~ ^"distinct") | ^"except") ~ Select }
        UnionAllContinuation = { ^"union" ~ ^"all" ~ Select }
    Select = {
        ^"select" ~ Projection ~ ^"from" ~ Scan ~
        Join? ~ WhereClause? ~
        (^"group" ~ ^"by" ~ GroupBy)? ~
        (^"having" ~ Having)?
    }
        Projection = { Distinct? ~ ProjectionElement ~ ("," ~ ProjectionElement)* }
            ProjectionElement = _{ Asterisk | Column }
            Column = { Expr ~ ((^"as")? ~ Identifier)? }
            Asterisk = { "*" }
        WhereClause = _{ ^"where" ~ Selection }
        Selection = { Expr }
        Scan = { (Identifier | SubQuery) ~ ((^"as")? ~ Identifier)? }
        Join = { JoinKind? ~ ^"join" ~ Scan ~ ^"on" ~ Expr }
            JoinKind = _{ ( InnerJoinKind | LeftJoinKind ) }
                InnerJoinKind = { ^"inner" }
                LeftJoinKind = { ^"left" ~ (^"outer")? }
        GroupBy = { Expr ~ ("," ~ Expr)* }
        Having = { Expr }
    SubQuery = { "(" ~ (SelectWithOptionalContinuation | Values) ~ ")" }
    Insert = { ^"insert" ~ ^"into" ~ Identifier ~ ("(" ~ TargetColumns ~ ")")? ~ (Values | Select) ~ OnConflict? }
        TargetColumns = { Identifier ~ ("," ~ Identifier)* }
        OnConflict = _{ ^"on conflict" ~ ^"do" ~ (DoNothing | DoReplace | DoFail) }
        DoReplace = { ^"replace" }
        DoNothing = { ^"nothing" }
        DoFail = { ^"fail" }
    Update = { ^"update" ~ Identifier ~ ^"set" ~ UpdateList ~ (UpdateFrom | WhereClause)? }
        UpdateList = { UpdateItem ~ ("," ~ UpdateItem)* }
        UpdateItem = { Identifier ~ "=" ~ Expr }
        UpdateFrom = _{ ^"from" ~ Scan ~ (^"where" ~ Expr)? }
    Values = { ^"values" ~ Row ~ ("," ~ Row)* }
    Delete = { ^"delete" ~ ^"from" ~ Identifier ~ (^"where" ~ DeleteFilter)? }
    	DeleteFilter = { Expr }

Identifier = ${ DoubleQuotedIdentifier | UnquotedIdentifier }
    DoubleQuotedIdentifier = @{ "\"" ~ ("\"\"" | '\u{01}'..'\u{21}' | '\u{23}'..'\u{10FFFF}')+ ~ "\"" }
    UnquotedIdentifier = @{ !(Keyword ~ ("(" | "[" | WHITESPACE | "," | EOF)) ~ (UnquotedIdentifierStart ~ UnquotedIdentifierRemainder*) }
        UnquotedIdentifierStart = _{ 'a'..'я' | 'A'..'Я' | "_" }
        UnquotedIdentifierRemainder = _{ UnquotedIdentifierStart | "$" | ASCII_DIGIT }
    Keyword = { ^"left" | ^"having" | ^"not" | ^"inner" | ^"group"
                | ^"on" | ^"join" | ^"from" | ^"exists" | ^"except"
                | ^"union" | ^"where" | ^"distinct" | ^"between" | ^"option"
                | ^"values" | ^"with" | ^"as" | ^"array" | ^"any" | ^"some"
                | ^"all" | ^"in" }

ExprRoot = _{ &SOI ~ Expr ~ &EOI }
Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* }
    ExprInfixOp = _{ Between | NonCmpInfixOp | CmpInfixOp | ConcatInfixOp | And | Or }
        Between       = { NotFlag? ~ ^"between" }
        And           = { ^"and" }
        Or            = { ^"or" }
        ConcatInfixOp = { "||" }
        CmpInfixOp    = { (NotEq | GtEq | Gt | LtEq | Lt | Eq | Lt) ~ (CmpArrayModifier ~ &ExpressionInParentheses)? }
            Eq    = { "=" }
            Gt    = { ">" }
            GtEq  = { ">=" }
            Lt    = { "<" }
            LtEq  = { "<=" }
            NotEq = { "<>" | "!=" }
        NonCmpInfixOp  = _{ Add | Subtract | Multiply | Divide | In }
            Add      = { "+" }
            Subtract = { "-" }
            Multiply = { "*" }
            Divide   = { "/" }
            In    = { NotFlag? ~ ^"in" }
        CmpArrayModifier = { CmpModifierAny | CmpModifierAll }
            CmpModifierAny = { ^"any" | ^"some "}
            CmpModifierAll = { ^"all" }
    ExprAtomValue = _{ UnaryNot* ~ AtomicExpr ~ IsNullPostfix? }
        UnaryNot   = @{ NotFlag }
        IsNullPostfix = { ^"is" ~ NotFlag? ~ ^"null" }
        AtomicExpr = _{ Literal | Parameter | IdentifierWithOptionalContinuation | ExpressionInParentheses | UnaryOperator | SubQuery | Row | SquareBracketArray }
            // TODO: Empty arrays don't parse without the `!"]"` prefix in the
            // optional sequence of sub-expressions, but the reason is not
            // immediately clear: the ']' character doesn't seem like it should
            // be compatible with the beginning of any `AtomicExpr`. This may
            // be worth investigating.
            SquareBracketArray = { ^"array" ~ "[" ~ (!"]" ~ (Expr ~ ("," ~ Expr)*))? ~ "]" }
            Literal = _{ True | False | Null | Double | Decimal | Unsigned | Integer | SingleQuotedString }
                True     = { ^"true" }
                False    = { ^"false" }
                Null     = { ^"null" }
                Decimal = @{ Integer ~ ("." ~ ASCII_DIGIT*) }
                Double = @{ Integer ~ ("." ~ ASCII_DIGIT*)? ~ (^"e" ~ Integer) }
                Integer = @{ ("+" | "-")? ~ ASCII_DIGIT+ }
                Unsigned = @{ ASCII_DIGIT+ }
                // TODO: Handle dollar-quoted string literals.
                SingleQuotedString = @{ "'" ~ ("''" | (!("'") ~ ANY))* ~ "'" }
            Parameter = @{ "$" ~ Unsigned }
            // Postgres permits qualified object names with a single identifier
            // part, 2 parts plus a function invocation, 3 parts, or 3 parts
            // plus a function invocation. For simplicity, assume that an
            // arbitrary number of qualifications (e.g. "a.b.c.d[...]") are
            // supported.
            // TODO: Disallow whitespace where it shouldn't be.
            IdentifierWithOptionalContinuation = { Identifier ~ QualifiedIdentifierContinuation* ~ FunctionInvocationContinuation? }
                QualifiedIdentifierContinuation = ${ "." ~ Identifier }
                FunctionInvocationContinuation = { "(" ~ (CountAsterisk | FunctionArgs)? ~ ")" }
                    // TODO: Support named argument notation
                    // (`my_func(name => value)`).
                    // TODO: Support keywords within args list as applicable.
                    FunctionArgs = { Distinct? ~ (Expr ~ ("," ~ Expr)*)? }
                    CountAsterisk = { "*" }
            ExpressionInParentheses = { "(" ~ Expr ~ ")" }
            CastInfix = { Expr ~ "::" ~ TypeCast }
                TypeCast = {
                    TypeBool
                    | TypeDecimal
                    | TypeDouble
                    | TypeInt
                    | TypeNumeric
                    | TypeText
                    | TypeVarchar
                }
                    TypeBool = { (^"boolean" | ^"bool") }
                    TypeDecimal = { ^"decimal" }
                    TypeDouble = { ^"double" }
                    TypeInt = { (^"integer" | ^"int") }
                    TypeNumeric = { ^"numeric" }
                    TypeText = { ^"text" }
                    TypeVarchar = { ^"varchar" ~ "(" ~ Unsigned ~ ")" }

                    TypeDate = { ^"date" }
                    TypeTime = { ^"time" ~ Unsigned? ~ (WithTimeZone | WithoutTimeZone)? }
                    TypeTimestamp = { ^"timestamp" ~ Unsigned? ~ (WithTimeZone | WithoutTimeZone)? }
                        WithTimeZone = { ^"with" ~ ^"time" ~ ^"zone" }
                        WithoutTimeZone = { ^"without" ~ ^"time" ~ ^"zone" }

            UnaryOperator = _{ Exists }
                Exists = { NotFlag? ~ ^"exists" ~ SubQuery }
            Row = { "(" ~ Expr ~ ("," ~ Expr)* ~ ")" }

Distinct = { ^"distinct" }
NotFlag = { ^"not" }
EOF = { EOI | ";" }
WHITESPACE = _{ " " | "\t" | "\n" | "\r\n" }
