module Selekt

  grammar SQL

    rule sql_query
      space? select_statement (space order_by_clause)? (space limit_clause)? space?
    end

    rule select_statement
      select_clause 
      (space from_clause)? 
      (space where_clause)? 
      (space group_by_clause)? 
      (space having_clause)? 
      (space named_window_clause)?
      (space set_operator space select_statement)*
    end

    rule select_clause
      SELECT space (DISTINCT space)? projection (comma projection)*
    end

    rule from_clause
      FROM space source_with_joins (comma source_with_joins)*
      /
      FROM space source_with_joins (comma source_with_joins)*
    end

    rule source_with_joins
       source (space source_join)*
       /
       '(' source (space source_join)* ')'
    end

    rule source
      ((table <TableReference>) (alias <Alias>)? / subquery (alias <Alias>)) <Source>
    end

    rule source_join
      regular_join_type space source space join_clause
    end

    rule regular_join_type
      ((INNER / LEFT / RIGHT / FULL space OUTER) space)? JOIN
    end

    rule join_clause
      ON space boolean_expression
      /
      USING space? '(' space? identifier (comma identifier)* ')'
    end

    rule comma
      space? ',' space?
    end

    rule subquery
      '(' space? select_statement space? ')'
    end

    rule table
      schema_table / schemaless_table
    end

    rule schemaless_table
      identifier {
        def schema_name
          nil
        end

        def table_name
          elements[0].value
        end
      }
    end

    rule schema_table
      schema '.' identifier {
        def schema_name
          schema.value
        end

        def table_name
          elements.last.value
        end
      }
    end

    rule schema
      identifier
    end

    rule column
      table_column / identifier
    end

    rule table_column
      identifier '.' identifier
    end

    rule projection
      '*' / table '.' '*' / expression alias?
    end

    rule alias
      space (AS space)? identifier 
    end

    rule where_clause
      WHERE space expression
    end

    rule group_by_clause
      GROUP space BY space expression (comma expression)*
    end

    rule having_clause
      HAVING space expression
    end

    rule order_by_clause
      ORDER space BY space order_expression (comma order_expression)*
    end

    rule limit_clause
      LIMIT space integer (space OFFSET space integer)?
    end

    rule order_expression
      expression (space (ASC / DESC) (space NULLS space (FIRST / LAST))?)?
    end

    rule expression
      boolean_expression
    end

    rule single_expression
      (interval_expression / case_expression / function_call / column / literal / subquery / '(' space? expression space? ')') 
      (space? '::' space? type)?
    end

    rule case_expression
      CASE space
        (expression space)? 
        (WHEN space expression space THEN space expression space)+
        (ELSE space expression space)? 
      END
    end

    rule interval_expression
      INTERVAL space expression
    end

    rule type
      unquoted_identifier
    end

    rule function_call
      (
        COUNT '(' space? (DISTINCT space)? ('*'  / expression) space? ')'
        /
        unquoted_identifier '(' (space? expression (comma expression)*)? space? ')'
      )
      (space OVER (space? window_clause / space identifier))?
    end

    rule window_clause
      '(' space?
        (partition_by_clause space)?
        order_by_clause
      space? ')'
    end

    rule named_window_clause
      WINDOW space identifier space AS space? window_clause
    end

    rule partition_by_clause
      PARTITION space BY space expression (comma expression)*
    end

    rule arithmetic_expression
      single_expression (space? arithmetic_operator space? single_expression)*
    end

    rule arithmetic_operator
      '+' / '-' / '*' / '/' / '%' / '||'
    end

    rule comparison_expression
      EXISTS space? subquery
      /
      arithmetic_expression space IS (space NOT)? space boolean
      /
      arithmetic_expression space (NOT space)? IN space list_of_values
      /
      arithmetic_expression (space? comparison_operator space? arithmetic_expression)?
    end

    rule list_of_values
      subquery
      /
      '(' space? expression (comma expression)* space? ')'
    end

    rule comparison_operator
      '<=' / '>=' / '<>' / '>' / '<' / '!=' / '=' / LIKE / ILIKE
    end

    rule negatable_expression
      (NOT space)? comparison_expression
    end

    rule boolean_expression
      negatable_expression (space boolean_operator space negatable_expression)*
    end

    rule boolean_operator
      AND / OR
    end

    rule set_operator
      UNION (space ALL)?
    end

    rule space
      [ \t\r\n]+ (comment space?)?
    end

    rule comment
      '--' [^\r\n]*
    end

    rule identifier
      quoted_identifier / unquoted_identifier !{|seq| seq[0].reserved? } {
        def value
          elements.first.value
        end
      }
    end

    rule unquoted_identifier
      [A-Za-z] [_A-Za-z0-9]*
      {
        def value
          text_value
        end

        def reserved?
          Selekt::RESERVED_SQL_KEYWORDS.include?(text_value.downcase)
        end
      }
    end

    rule quoted_identifier
      '"' body:('""' / [^"])* '"' {
        def value
          body.text_value.gsub('""', '"')
        end
      }
    end

    rule literal
      string / float / integer / boolean
    end

    rule string
      "'" ("''" / [^'])* "'"
    end

    rule boolean
      TRUE / FALSE / NULL
    end

    rule integer
      "-"? [0-9]+
    end

    rule float
      "-"? [0-9]* '.' [0-9]+
    end

    rule SELECT
      [Ss] [Ee] [Ll] [Ee] [Cc] [Tt]      
    end

    rule DISTINCT
      [Dd] [Ii] [Ss] [Tt] [Ii] [Nn] [Cc] [Tt]
    end

    rule FROM
      [Ff] [Rr] [Oo] [Mm]
    end

    rule HAVING
      [Hh] [Aa] [Vv] [Ii] [Nn] [Gg]
    end

    rule ORDER
      [Oo] [Rr] [Dd] [Ee] [Rr]
    end

    rule ASC
      [Aa] [Ss] [Cc]
    end

    rule DESC
      [Dd] [Ee] [Ss] [Cc]
    end

    rule NULLS
      [Nn] [Uu] [Ll] [Ll] [Ss]
    end

    rule FIRST
      [Ff] [Ii] [Rr] [Ss] [Tt]
    end

    rule LAST
      [Ll] [Aa] [Ss] [Tt]
    end

    rule AS
      [Aa] [Ss]
    end

    rule JOIN
      [Jj] [Oo] [Ii] [Nn]
    end

    rule FULL
      [Ff] [Uu] [Ll] [Ll]
    end

    rule OUTER
      [Oo] [Uu] [Tt] [Ee] [Rr]
    end

    rule INNER
      [Ii] [Nn] [Nn] [Ee] [Rr]
    end

    rule LEFT
      [Ll] [Ee] [Ff] [Tt]
    end

    rule RIGHT
      [Rr] [Ii] [Gg] [Hh] [Tt]
    end

    rule ON
      [Oo] [Nn]
    end

    rule USING
      [Uu] [Ss] [Ii] [Nn] [Gg]
    end

    rule WHERE
      [Ww] [Hh] [Ee] [Rr] [Ee]
    end

    rule GROUP
      [Gg] [Rr] [Oo] [Uu] [Pp]
    end

    rule BY
      [Bb] [Yy]
    end

    rule LIMIT
      [Ll] [Ii] [Mm] [Ii] [Tt]
    end

    rule OFFSET
      [Oo] [Ff] [Ff] [Ss] [Ee] [Tt]
    end

    rule UNION
      [Uu] [Nn] [Ii] [Oo] [Nn]
    end

    rule ALL
      [Aa] [Ll] [Ll]
    end

    rule COUNT
      [Cc] [Oo] [Uu] [Nn] [Tt]
    end

    rule OVER
      [Oo] [Vv] [Ee] [Rr]
    end

    rule PARTITION
      [Pp] [Aa] [Rr] [Tt] [Ii] [Tt] [Ii] [Oo] [Nn]
    end

    rule WINDOW
      [Ww] [Ii] [Nn] [Dd] [Oo] [Ww]
    end

    rule CASE
      [Cc] [Aa] [Ss] [Ee]
    end

    rule WHEN
      [Ww] [Hh] [Ee] [Nn]
    end

    rule THEN
      [Tt] [Hh] [Ee] [Nn]
    end

    rule ELSE
      [Ee] [Ll] [Ss] [Ee]
    end

    rule END
      [Ee] [Nn] [Dd]
    end

    rule INTERVAL
      [Ii] [Nn] [Tt] [Ee] [Rr] [Vv] [Aa] [Ll]
    end

    rule IS
      [Ii] [Ss]
    end

    rule IN
      [Ii] [Nn]
    end

    rule EXISTS
      [Ee] [Xx] [Ii] [Ss] [Tt] [Ss]
    end

    rule NOT
      [Nn] [Oo] [Tt]
    end

    rule AND
      [Aa] [Nn] [Dd]
    end

    rule OR
      [Oo] [Rr]
    end    

    rule TRUE
      [Tt] [Rr] [Uu] [Ee]
    end

    rule FALSE
      [Ff] [Aa] [Ll] [Ss] [Ee]
    end

    rule NULL
      [Nn] [Uu] [Ll] [Ll]
    end

    rule LIKE
      [Ll] [Ii] [Kk] [Ee]
    end

    rule ILIKE
      [Ii] [Ll] [Ii] [Kk] [Ee]
    end
  end
end