/*
 * SPDX-License-Identifier: Apache-2.0
 */

grammar Cql;

options {
    language = Cpp;
}

@parser::namespace{cql3_parser}

@lexer::includes {
#include "cql3/error_collector.hh"
#include "cql3/error_listener.hh"
}

@parser::includes {
#include "cql3/statements/raw/parsed_statement.hh"
#include "cql3/statements/raw/select_statement.hh"
#include "cql3/statements/alter_keyspace_statement.hh"
#include "cql3/statements/alter_table_statement.hh"
#include "cql3/statements/alter_view_statement.hh"
#include "cql3/statements/alter_service_level_statement.hh"
#include "cql3/statements/create_keyspace_statement.hh"
#include "cql3/statements/drop_keyspace_statement.hh"
#include "cql3/statements/create_index_statement.hh"
#include "cql3/statements/create_table_statement.hh"
#include "cql3/statements/create_view_statement.hh"
#include "cql3/statements/create_type_statement.hh"
#include "cql3/statements/create_function_statement.hh"
#include "cql3/statements/create_aggregate_statement.hh"
#include "cql3/statements/create_service_level_statement.hh"
#include "cql3/statements/sl_prop_defs.hh"
#include "cql3/statements/attach_service_level_statement.hh"
#include "cql3/statements/drop_type_statement.hh"
#include "cql3/statements/alter_type_statement.hh"
#include "cql3/statements/property_definitions.hh"
#include "cql3/statements/drop_index_statement.hh"
#include "cql3/statements/drop_table_statement.hh"
#include "cql3/statements/drop_view_statement.hh"
#include "cql3/statements/drop_function_statement.hh"
#include "cql3/statements/drop_aggregate_statement.hh"
#include "cql3/statements/drop_service_level_statement.hh"
#include "cql3/statements/detach_service_level_statement.hh"
#include "cql3/statements/raw/truncate_statement.hh"
#include "cql3/statements/raw/update_statement.hh"
#include "cql3/statements/raw/insert_statement.hh"
#include "cql3/statements/raw/delete_statement.hh"
#include "cql3/statements/index_prop_defs.hh"
#include "cql3/statements/raw/use_statement.hh"
#include "cql3/statements/raw/batch_statement.hh"
#include "cql3/statements/raw/describe_statement.hh"
#include "cql3/statements/list_users_statement.hh"
#include "cql3/statements/grant_statement.hh"
#include "cql3/statements/revoke_statement.hh"
#include "cql3/statements/list_permissions_statement.hh"
#include "cql3/statements/alter_role_statement.hh"
#include "cql3/statements/list_roles_statement.hh"
#include "cql3/statements/list_service_level_statement.hh"
#include "cql3/statements/list_service_level_attachments_statement.hh"
#include "cql3/statements/list_effective_service_level_statement.hh"
#include "cql3/statements/grant_role_statement.hh"
#include "cql3/statements/revoke_role_statement.hh"
#include "cql3/statements/drop_role_statement.hh"
#include "cql3/statements/create_role_statement.hh"
#include "cql3/statements/index_target.hh"
#include "cql3/statements/ks_prop_defs.hh"
#include "cql3/selection/raw_selector.hh"
#include "cql3/selection/selectable-expr.hh"
#include "cql3/dialect.hh"
#include "cql3/keyspace_element_name.hh"
#include "cql3/constants.hh"
#include "cql3/operation_impl.hh"
#include "cql3/error_listener.hh"
#include "cql3/index_name.hh"
#include "cql3/cql3_type.hh"
#include "cql3/cf_name.hh"
#include "cql3/maps.hh"
#include "cql3/sets.hh"
#include "cql3/lists.hh"
#include "cql3/role_name.hh"
#include "cql3/role_options.hh"
#include "cql3/user_types.hh"
#include "cql3/ut_name.hh"
#include "cql3/functions/function_name.hh"
#include "cql3/expr/expression.hh"
#include <seastar/core/sstring.hh>
#include "CqlLexer.hpp"

#include <algorithm>
#include <unordered_map>
#include <map>
}

@parser::traits {

using namespace cql3;
using namespace cql3::statements;
using namespace cql3::selection;
using namespace cql3::expr;

using cql3::cql3_type;
using operations_type = std::vector<std::pair<::shared_ptr<cql3::column_identifier::raw>, std::unique_ptr<cql3::operation::raw_update>>>;

// ANTLR forces us to define a default-initialized return value
// for every rule (e.g. [returns ut_name name]), but not every type
// can be naturally zero-initialized.
//
// The uninitialized<T> wrapper can be zero-initialized, and is convertible
// to T (after checking that it was assigned to) implicitly, eliminating the
// problem.  It is up to the user to ensure it is actually assigned to. 
template <typename T>
struct uninitialized {
    std::optional<T> _val;
    uninitialized() = default;
    uninitialized(const uninitialized&) = default;
    uninitialized(uninitialized&&) = default;
    uninitialized(const T& val) : _val(val) {}
    uninitialized(T&& val) : _val(std::move(val)) {}
    uninitialized(std::convertible_to<T> auto&& x) : _val(std::forward<decltype(x)>(x)) {}
    uninitialized& operator=(const uninitialized&) = default;
    uninitialized& operator=(uninitialized&&) = default;
    operator T&&() && { return check(), std::move(*_val); }
    operator std::optional<T>&&() && { return check(), std::move(_val); }
    void check() const { if (!_val) { throw std::runtime_error("not initialized"); } }
};

template <typename T>
struct unwrap_uninitialized {
    using type = T;
};

template <typename T>
struct unwrap_uninitialized<uninitialized<T>> {
    using type = T;
};

template <typename T>
using unwrap_uninitialized_t = typename unwrap_uninitialized<T>::type;

using uexpression = uninitialized<expression>;

}

@context {
    using collector_type = cql3::error_collector<ComponentType, ExceptionBaseType::TokenType, ExceptionBaseType>;
    using listener_type = cql3::error_listener<ComponentType, ExceptionBaseType>;

    listener_type* listener;

    dialect _dialect;

    // Keeps the names of all bind variables. For bind variables without a name ('?'), the name is nullptr.
    // Maps bind_index -> name.
    std::vector<::shared_ptr<cql3::column_identifier>> _bind_variable_names;

    // Maps name -> bind_index for all named bind variables.
    std::unordered_map<cql3::column_identifier, size_t> _named_bind_variables_indexes;
    std::vector<std::unique_ptr<TokenType>> _missing_tokens;

    // Can't use static variable, since it needs to be defined out-of-line
    static const std::unordered_set<sstring>& _reserved_type_names() {
        static std::unordered_set<sstring> s = {
            "byte",
            "smallint",
            "complex",
            "enum",
            "date",
            "interval",
            "macaddr",
            "bitstring",
        };
        return s;
    }

    void set_dialect(dialect d) {
        _dialect = d;
    }

    bind_variable new_bind_variables(shared_ptr<cql3::column_identifier> name)
    {
        if (_dialect.duplicate_bind_variable_names_refer_to_same_variable
                && name && _named_bind_variables_indexes.contains(*name)) {
            return bind_variable{_named_bind_variables_indexes[*name]};
        }
        auto marker = bind_variable{_bind_variable_names.size()};
        _bind_variable_names.push_back(name);
        if (name) {
            _named_bind_variables_indexes[*name] = marker.bind_index;
        }
        return marker;
    }


    void set_error_listener(listener_type& listener) {
        this->listener = &listener;
    }

    void displayRecognitionError(ANTLR_UINT8** token_names, ExceptionBaseType* ex)
    {
        listener->syntax_error(*this, token_names, ex);
    }

	void add_recognition_error(const sstring& msg) {
        listener->syntax_error(*this, msg);
    }

    bool is_eof_token(CommonTokenType token) const
    {
        return token == CommonTokenType::TOKEN_EOF;
    }

    std::string token_text(const TokenType* token)
    {
        if (!token) {
            return "";
        }
        return token->getText();
    }

    std::map<sstring, sstring> convert_property_map(const collection_constructor& map) {
        if (map.elements.empty()) {
            return std::map<sstring, sstring>{};
        }
        std::map<sstring, sstring> res;
        for (auto&& entry : map.elements) {
            auto entry_tuple = expr::as_if<tuple_constructor>(&entry);
            // Because the parser tries to be smart and recover on error (to
            // allow displaying more than one error I suppose), we have default-constructed
            // entries in map.elements. Just skip those, a proper error will be thrown in the end.
            if (!entry_tuple || entry_tuple->elements.size() != 2) {
                break;
            }
            auto left = expr::as_if<untyped_constant>(&entry_tuple->elements[0]);
            if (!left) {
                sstring msg = fmt::format("Invalid property name: {}", entry_tuple->elements[0]);
                if (expr::is<bind_variable>(entry_tuple->elements[0])) {
                    msg += " (bind variables are not supported in DDL queries)";
                }
                add_recognition_error(msg);
                break;
            }
            auto right = expr::as_if<untyped_constant>(&entry_tuple->elements[1]);
            if (!right) {
                sstring msg = fmt::format("Invalid property value: {} for property: {}", entry_tuple->elements[0], entry_tuple->elements[1]);
                if (expr::is<bind_variable>(entry_tuple->elements[1])) {
                    msg += " (bind variables are not supported in DDL queries)";
                }
                add_recognition_error(msg);
                break;
            }
            if (!res.emplace(left->raw_text, right->raw_text).second) {
                sstring msg = fmt::format("Multiple definition for property {}", left->raw_text);
                add_recognition_error(msg);
                break;
            }
        }
        return res;
    }

    sstring to_lower(std::string_view s) {
        sstring lower_s(s.size(), '\0');
        std::transform(s.cbegin(), s.cend(), lower_s.begin(), &::tolower);
        return lower_s;
    }

    bool convert_boolean_literal(std::string_view s) {
        return to_lower(s) == "true";
    }

    TokenType* getMissingSymbol(IntStreamType* istream, ExceptionBaseType* e,
                                ANTLR_UINT32 expectedTokenType, BitsetListType* follow) {
        auto token = BaseType::getMissingSymbol(istream, e, expectedTokenType, follow);
        _missing_tokens.emplace_back(token);
        return token;
    }
}

@lexer::namespace{cql3_parser}

@lexer::traits {
    class CqlLexer;
    class CqlParser;
    typedef antlr3::Traits<CqlLexer, CqlParser> CqlLexerTraits;
    typedef CqlLexerTraits CqlParserTraits;
}

@lexer::header {
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
}

@lexer::context {
    using collector_type = cql3::error_collector<ComponentType, ExceptionBaseType::TokenType, ExceptionBaseType>;
    using listener_type = cql3::error_listener<ComponentType, ExceptionBaseType>;

    listener_type* listener;

    void set_error_listener(listener_type& listener) {
        this->listener = &listener;
    }

    void displayRecognitionError(ANTLR_UINT8** token_names, ExceptionBaseType* ex)
    {
        listener->syntax_error(*this, token_names, ex);
    }

    bool is_eof_token(CommonTokenType token) const
    {
        return token == CommonTokenType::TOKEN_EOF;
    }

    std::string token_text(const TokenType* token) const
    {
        if (!token) {
            return "";
        }
        return std::to_string(int(*token));
    }
}

/** STATEMENTS **/

queries returns [std::vector<std::unique_ptr<raw::parsed_statement>> stmts]
    : st=cqlStatement { $stmts.emplace_back(std::move(st)); }
      (';' st=cqlStatement { $stmts.emplace_back(std::move(st)); })*
      (';')* EOF
    ;

query returns [std::unique_ptr<raw::parsed_statement> stmnt]
    : st=cqlStatement (';')* EOF { $stmnt = std::move(st); }
    ;

cqlStatement returns [std::unique_ptr<raw::parsed_statement> stmt]
    @after{ if (stmt) { stmt->set_bound_variables(_bind_variable_names); } }
    : st1= selectStatement             { $stmt = std::move(st1); }
    | st2= insertStatement             { $stmt = std::move(st2); }
    | st3= updateStatement             { $stmt = std::move(st3); }
    | st4= batchStatement              { $stmt = std::move(st4); }
    | st5= deleteStatement             { $stmt = std::move(st5); }
    | st6= useStatement                { $stmt = std::move(st6); }
    | st7= truncateStatement           { $stmt = std::move(st7); }
    | st8= createKeyspaceStatement     { $stmt = std::move(st8); }
    | st9= createTableStatement        { $stmt = std::move(st9); }
    | st10=createIndexStatement        { $stmt = std::move(st10); }
    | st11=dropKeyspaceStatement       { $stmt = std::move(st11); }
    | st12=dropTableStatement          { $stmt = std::move(st12); }
    | st13=dropIndexStatement          { $stmt = std::move(st13); }
    | st14=alterTableStatement         { $stmt = std::move(st14); }
    | st15=alterKeyspaceStatement      { $stmt = std::move(st15); }
    | st16=grantStatement              { $stmt = std::move(st16); }
    | st17=revokeStatement             { $stmt = std::move(st17); }
    | st18=listPermissionsStatement    { $stmt = std::move(st18); }
    | st19=createUserStatement         { $stmt = std::move(st19); }
    | st20=alterUserStatement          { $stmt = std::move(st20); }
    | st21=dropUserStatement           { $stmt = std::move(st21); }
    | st22=listUsersStatement          { $stmt = std::move(st22); }
#if 0
    | st23=createTriggerStatement      { $stmt = st23; }
    | st24=dropTriggerStatement        { $stmt = st24; }
#endif
    | st25=createTypeStatement         { $stmt = std::move(st25); }
    | st26=alterTypeStatement          { $stmt = std::move(st26); }
    | st27=dropTypeStatement           { $stmt = std::move(st27); }
    | st28=createFunctionStatement     { $stmt = std::move(st28); }
    | st29=dropFunctionStatement       { $stmt = std::move(st29); }
    | st30=createAggregateStatement    { $stmt = std::move(st30); }
    | st31=dropAggregateStatement      { $stmt = std::move(st31); }
    | st32=createViewStatement         { $stmt = std::move(st32); }
    | st33=alterViewStatement          { $stmt = std::move(st33); }
    | st34=dropViewStatement           { $stmt = std::move(st34); }
    | st35=listRolesStatement          { $stmt = std::move(st35); }
    | st36=grantRoleStatement          { $stmt = std::move(st36); }
    | st37=revokeRoleStatement         { $stmt = std::move(st37); }
    | st38=dropRoleStatement           { $stmt = std::move(st38); }
    | st39=createRoleStatement         { $stmt = std::move(st39); }
    | st40=alterRoleStatement          { $stmt = std::move(st40); }
    | st41=createServiceLevelStatement { $stmt = std::move(st41); }
    | st42=alterServiceLevelStatement  { $stmt = std::move(st42); }
    | st43=dropServiceLevelStatement   { $stmt = std::move(st43); }
    | st44=attachServiceLevelStatement { $stmt = std::move(st44); }
    | st45=detachServiceLevelStatement { $stmt = std::move(st45); }
    | st46=listServiceLevelStatement { $stmt = std::move(st46); }
    | st47=listServiceLevelAttachStatement { $stmt = std::move(st47); }
    | st48=pruneMaterializedViewStatement  { $stmt = std::move(st48); }
    | st49=describeStatement           { $stmt = std::move(st49); }
    | st50=listEffectiveServiceLevelStatement { $stmt = std::move(st50); }
    ;

/*
 * USE <KEYSPACE>;
 */
useStatement returns [std::unique_ptr<raw::use_statement> stmt]
    : K_USE ks=keyspaceName { $stmt = std::make_unique<raw::use_statement>(ks); }
    ;

/**
 * SELECT [JSON] <expression>
 * FROM <CF>
 * WHERE KEY = "key1" AND COL > 1 AND COL < 100
 * LIMIT <NUMBER>
 * [USING TIMEOUT <duration>];
 */
selectStatement returns [std::unique_ptr<raw::select_statement> expr]
    @init {
        bool is_distinct = false;
        std::optional<expression> limit;
        std::optional<expression> per_partition_limit;
        raw::select_statement::parameters::orderings_type orderings;
        bool allow_filtering = false;
        raw::select_statement::parameters::statement_subtype statement_subtype = raw::select_statement::parameters::statement_subtype::REGULAR;
        bool bypass_cache = false;
        auto attrs = std::make_unique<cql3::attributes::raw>();
        expression wclause = conjunction{};
    }
    : K_SELECT (
                ( K_JSON { statement_subtype = raw::select_statement::parameters::statement_subtype::JSON; } )?
                ( K_DISTINCT { is_distinct = true; } )?
                sclause=selectClause
               )
      K_FROM (
                cf=columnFamilyName
                | K_MUTATION_FRAGMENTS '(' cf=columnFamilyName ')' { statement_subtype = raw::select_statement::parameters::statement_subtype::MUTATION_FRAGMENTS; }
             )
      ( K_WHERE w=whereClause { wclause = std::move(w); } )?
      ( K_GROUP K_BY gbcolumns=listOfIdentifiers)?
      ( K_ORDER K_BY orderByClause[orderings] ( ',' orderByClause[orderings] )* )?
      ( K_PER K_PARTITION K_LIMIT rows=intValue { per_partition_limit = std::move(rows); } )?
      ( K_LIMIT rows=intValue { limit = std::move(rows); } )?
      ( K_ALLOW K_FILTERING  { allow_filtering = true; } )?
      ( K_BYPASS K_CACHE { bypass_cache = true; })?
      ( usingTimeoutServiceLevelClause[attrs] )?
      {
          auto params = make_lw_shared<raw::select_statement::parameters>(std::move(orderings), is_distinct, allow_filtering, statement_subtype, bypass_cache);
          $expr = std::make_unique<raw::select_statement>(std::move(cf), std::move(params),
            std::move(sclause), std::move(wclause), std::move(limit), std::move(per_partition_limit),
            std::move(gbcolumns), std::move(attrs));
      }
    ;

selectClause returns [std::vector<shared_ptr<raw_selector>> expr]
    : t1=selector { $expr.push_back(t1); } (',' tN=selector { $expr.push_back(tN); })*
    | '*' { }
    ;

selector returns [shared_ptr<raw_selector> s]
    @init{ shared_ptr<cql3::column_identifier> alias; }
    : us=unaliasedSelector (K_AS c=ident { alias = c; })? { $s = ::make_shared<raw_selector>(std::move(us), alias); }
    ;

unaliasedSelector returns [uexpression tmp]
    :  ( c=cident                                  { tmp = unresolved_identifier{std::move(c)}; }
       | K_COUNT '(' countArgument ')'             { tmp = make_count_rows_function_expression(); }
       | K_WRITETIME '(' c=cident ')'              { tmp = column_mutation_attribute{column_mutation_attribute::attribute_kind::writetime,
                                                                                              unresolved_identifier{std::move(c)}}; }
       | K_TTL       '(' c=cident ')'              { tmp = column_mutation_attribute{column_mutation_attribute::attribute_kind::ttl,
                                                                                              unresolved_identifier{std::move(c)}}; }
       | f=functionName args=selectionFunctionArgs { tmp = function_call{std::move(f), std::move(args)}; }
       | K_CAST      '(' arg=unaliasedSelector K_AS t=native_type ')'  { tmp = cast{.style = cast::cast_style::sql, .arg = std::move(arg), .type = std::move(t)}; }
       )
       ( '.' fi=cident { tmp = field_selection{std::move(tmp), std::move(fi)}; }
       | '[' sub=term ']' { tmp = subscript{std::move(tmp), std::move(sub)}; }
       )*
    ;

selectionFunctionArgs returns [std::vector<expression> a]
    : '(' ')'
    | '(' s1=unaliasedSelector { a.push_back(std::move(s1)); }
          ( ',' sn=unaliasedSelector { a.push_back(std::move(sn)); } )*
      ')'
    ;

countArgument
    : '*'
    | i=INTEGER { if (i->getText() != "1") {
                    add_recognition_error("Only COUNT(1) is supported, got COUNT(" + i->getText() + ")");
                } }
    ;

whereClause returns [uexpression clause]
    @init { std::vector<expression> terms; }
    : e1=relation { terms.push_back(std::move(e1)); } (K_AND en=relation { terms.push_back(std::move(en)); })*
        { clause = conjunction{std::move(terms)}; }
    ;

orderByClause[raw::select_statement::parameters::orderings_type& orderings]
    @init{
        raw::select_statement::ordering ordering = raw::select_statement::ordering::ascending;
    }
    : c=cident (K_ASC | K_DESC { ordering = raw::select_statement::ordering::descending; })? { orderings.emplace_back(c, ordering); }
    ;

jsonValue returns [uexpression value]
    : s=STRING_LITERAL { $value = untyped_constant{untyped_constant::string, $s.text}; }
    | m=marker         { $value = std::move(m); }
    ;

/**
 * INSERT INTO <CF> (<column>, <column>, <column>, ...)
 * VALUES (<value>, <value>, <value>, ...)
 * USING TIMESTAMP <long>;
 *
 */
insertStatement returns [std::unique_ptr<raw::modification_statement> expr]
    @init {
        auto attrs = std::make_unique<cql3::attributes::raw>();
        std::vector<::shared_ptr<cql3::column_identifier::raw>> column_names;
        std::vector<expression> values;
        bool if_not_exists = false;
        bool default_unset = false;
        std::optional<expression> json_value;
    }
    : K_INSERT K_INTO cf=columnFamilyName
        ('(' c1=cident { column_names.push_back(c1); }  ( ',' cn=cident { column_names.push_back(cn); } )* ')'
            K_VALUES
            '(' v1=term { values.push_back(std::move(v1)); } ( ',' vn=term { values.push_back(std::move(vn)); } )* ')'
            ( K_IF K_NOT K_EXISTS { if_not_exists = true; } )?
            ( usingClause[attrs] )?
              {
              $expr = std::make_unique<raw::insert_statement>(std::move(cf),
                                                       std::move(attrs),
                                                       std::move(column_names),
                                                       std::move(values),
                                                       if_not_exists);
              }
        | K_JSON
          json_token=jsonValue { json_value = std::move(json_token); }
            ( K_DEFAULT K_UNSET { default_unset = true; } | K_DEFAULT K_NULL )?
            ( K_IF K_NOT K_EXISTS { if_not_exists = true; } )?
            ( usingClause[attrs] )?
              {
              $expr = std::make_unique<raw::insert_json_statement>(std::move(cf),
                                                       std::move(attrs),
                                                       std::move(*json_value),
                                                       if_not_exists,
                                                       default_unset);
              }
        )
    ;

usingClause[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_USING usingClauseObjective[attrs] ( K_AND usingClauseObjective[attrs] )*
    ;

usingClauseObjective[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_TIMESTAMP ts=intValue { attrs->timestamp = std::move(ts); }
    | K_TTL t=intValue { attrs->time_to_live = std::move(t); }
    | K_TIMEOUT to=term { attrs->timeout = std::move(to); }
    ;

usingTimestampTimeoutClause[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_USING usingTimestampTimeoutClauseObjective[attrs] ( K_AND usingTimestampTimeoutClauseObjective[attrs] )*
    ;

usingTimestampTimeoutClauseObjective[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_TIMESTAMP ts=intValue { attrs->timestamp = std::move(ts); }
    | K_TIMEOUT to=term { attrs->timeout = std::move(to); }
    ;

usingTimeoutClause[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_USING K_TIMEOUT to=term { attrs->timeout = std::move(to); }
    ;

usingTimestampClause[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_USING K_TIMESTAMP ts=intValue { attrs->timestamp = std::move(ts); }
    ;

usingTimeoutServiceLevelClause[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_USING usingTimeoutServiceLevelClauseObjective[attrs] ( K_AND usingTimeoutServiceLevelClauseObjective[attrs] )*
    ;

usingTimeoutServiceLevelClauseObjective[std::unique_ptr<cql3::attributes::raw>& attrs]
    : K_TIMEOUT to=term { attrs->timeout = std::move(to); }
    | serviceLevel sl_name=serviceLevelOrRoleName { attrs->service_level = std::move(sl_name); }
    ;

/**
 * UPDATE <CF>
 * USING TIMESTAMP <long>
 * SET name1 = value1, name2 = value2
 * WHERE key = value;
 */
updateStatement returns [std::unique_ptr<raw::update_statement> expr]
    @init {
        bool if_exists = false;
        auto attrs = std::make_unique<cql3::attributes::raw>();
        std::vector<std::pair<::shared_ptr<cql3::column_identifier::raw>, std::unique_ptr<cql3::operation::raw_update>>> operations;
        std::optional<expression> cond_opt;
    }
    : K_UPDATE cf=columnFamilyName
      ( usingClause[attrs] )?
      K_SET columnOperation[operations] (',' columnOperation[operations])*
      K_WHERE wclause=whereClause
      ( K_IF (K_EXISTS{ if_exists = true; } | conditions=updateConditions { cond_opt = std::move(conditions); } ))?
      {
          return std::make_unique<raw::update_statement>(std::move(cf),
                                                  std::move(attrs),
                                                  std::move(operations),
                                                  std::move(wclause),
                                                  std::move(cond_opt),
                                                  if_exists);
     }
    ;

updateConditions returns [uexpression cond]
    @init {
        std::vector<expression> conditions;
    }
    : c1=columnCondition { conditions.emplace_back(std::move(c1)); }
         ( K_AND cn=columnCondition { conditions.emplace_back(std::move(cn)); } )*
    {
        return conjunction{std::move(conditions)};
    }
    ;

/**
 * DELETE name1, name2
 * FROM <CF>
 * [USING (TIMESTAMP <long> | TIMEOUT <duration>) [AND ...]]
 * WHERE KEY = keyname
   [IF (EXISTS | name = value, ...)];
 */
deleteStatement returns [std::unique_ptr<raw::delete_statement> expr]
    @init {
        auto attrs = std::make_unique<cql3::attributes::raw>();
        std::vector<std::unique_ptr<cql3::operation::raw_deletion>> column_deletions;
        bool if_exists = false;
        std::optional<expression> cond_opt;
    }
    : K_DELETE ( dels=deleteSelection { column_deletions = std::move(dels); } )?
      K_FROM cf=columnFamilyName
      ( usingTimestampTimeoutClause[attrs] )?
      K_WHERE wclause=whereClause
      ( K_IF ( K_EXISTS { if_exists = true; } | conditions=updateConditions { cond_opt = std::move(conditions); } ))?
      {
          return std::make_unique<raw::delete_statement>(cf,
                                            std::move(attrs),
                                            std::move(column_deletions),
                                            std::move(wclause),
                                            std::move(cond_opt),
                                            if_exists);
      }
    ;

deleteSelection returns [std::vector<std::unique_ptr<cql3::operation::raw_deletion>> operations]
    : t1=deleteOp { $operations.emplace_back(std::move(t1)); }
      (',' tN=deleteOp { $operations.emplace_back(std::move(tN)); })*
    ;

deleteOp returns [std::unique_ptr<cql3::operation::raw_deletion> op]
    : c=cident                { $op = std::make_unique<cql3::operation::column_deletion>(std::move(c)); }
    | c=cident '[' t=term ']' { $op = std::make_unique<cql3::operation::element_deletion>(std::move(c), std::move(t)); }
    | c=cident '.' field=ident { $op = std::make_unique<cql3::operation::field_deletion>(std::move(c), std::move(field)); }
    ;

pruneMaterializedViewStatement returns [std::unique_ptr<raw::select_statement> expr]
    @init {
        bool is_distinct = false;
        std::optional<expression> limit;
        std::optional<expression> per_partition_limit;
        raw::select_statement::parameters::orderings_type orderings;
        bool allow_filtering = false;
        raw::select_statement::parameters::statement_subtype statement_subtype = raw::select_statement::parameters::statement_subtype::PRUNE_MATERIALIZED_VIEW;
        bool bypass_cache = false;
        auto attrs = std::make_unique<cql3::attributes::raw>();
        expression wclause = conjunction{};
    }
	: K_PRUNE K_MATERIALIZED K_VIEW cf=columnFamilyName (K_WHERE w=whereClause { wclause = std::move(w); } )? ( usingClause[attrs] )?
	  {
	        auto params = make_lw_shared<raw::select_statement::parameters>(std::move(orderings), is_distinct, allow_filtering, statement_subtype, bypass_cache);
	        return std::make_unique<raw::select_statement>(std::move(cf), std::move(params),
            std::vector<shared_ptr<raw_selector>>(), std::move(wclause), std::move(limit), std::move(per_partition_limit),
            std::vector<::shared_ptr<cql3::column_identifier::raw>>(), std::move(attrs));
	  }
	;

/**
 * BEGIN BATCH
 *   UPDATE <CF> SET name1 = value1 WHERE KEY = keyname1;
 *   UPDATE <CF> SET name2 = value2 WHERE KEY = keyname2;
 *   UPDATE <CF> SET name3 = value3 WHERE KEY = keyname3;
 *   ...
 * APPLY BATCH
 *
 * OR
 *
 * BEGIN BATCH
 *   INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
 *   INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
 *   ...
 * APPLY BATCH
 *
 * OR
 *
 * BEGIN BATCH
 *   DELETE name1, name2 FROM <CF> WHERE key = <key>
 *   DELETE name3, name4 FROM <CF> WHERE key = <key>
 *   ...
 * APPLY BATCH
 */
batchStatement returns [std::unique_ptr<cql3::statements::raw::batch_statement> expr]
    @init {
        using btype = cql3::statements::raw::batch_statement::type; 
        btype type = btype::LOGGED;
        std::vector<std::unique_ptr<cql3::statements::raw::modification_statement>> statements;
        auto attrs = std::make_unique<cql3::attributes::raw>();
    }
    : K_BEGIN
      ( K_UNLOGGED { type = btype::UNLOGGED; } | K_COUNTER { type = btype::COUNTER; } )?
      K_BATCH ( usingClause[attrs] )?
          ( s=batchStatementObjective ';'? { statements.push_back(std::move(s)); } )*
      K_APPLY K_BATCH
      {
          $expr = std::make_unique<cql3::statements::raw::batch_statement>(type, std::move(attrs), std::move(statements));
      }
    ;

batchStatementObjective returns [std::unique_ptr<cql3::statements::raw::modification_statement> statement]
    : i=insertStatement  { $statement = std::move(i); }
    | u=updateStatement  { $statement = std::move(u); }
    | d=deleteStatement  { $statement = std::move(d); }
    ;

dropAggregateStatement returns [std::unique_ptr<cql3::statements::drop_aggregate_statement> expr]
    @init {
        bool if_exists = false;
        std::vector<shared_ptr<cql3_type::raw>> arg_types;
        bool args_present = false;
    }
    : K_DROP K_AGGREGATE
      (K_IF K_EXISTS { if_exists = true; } )?
      fn=functionName
      (
        '('
          (
            v=comparatorType { arg_types.push_back(v); }
            ( ',' v=comparatorType { arg_types.push_back(v); } )*
          )?
        ')'
        { args_present = true; }
      )?
      { $expr = std::make_unique<cql3::statements::drop_aggregate_statement>(std::move(fn), std::move(arg_types), args_present, if_exists); }
    ;

createAggregateStatement returns [std::unique_ptr<cql3::statements::create_aggregate_statement> expr]
    @init {
        bool or_replace = false;
        bool if_not_exists = false;

        std::vector<shared_ptr<cql3_type::raw>> arg_types;
        std::optional<sstring> ffunc;
        std::optional<expr::expression> ival;
        std::optional<sstring> rfunc;
    }
    : K_CREATE (K_OR K_REPLACE { or_replace = true; })?
      K_AGGREGATE
      (K_IF K_NOT K_EXISTS { if_not_exists = true; })?
      fn=functionName
      '('
        (
          v=comparatorType { arg_types.push_back(v); }
          ( ',' v=comparatorType { arg_types.push_back(v); } )*
        )?
      ')'
      K_SFUNC sfunc = allowedFunctionName
      K_STYPE stype = comparatorType
      (
        K_REDUCEFUNC reduce_name = allowedFunctionName { rfunc = reduce_name; }
      )?
      (
        K_FINALFUNC final_func = allowedFunctionName { ffunc = final_func; }
      )?
      (
        K_INITCOND init_val = term { ival = std::move(init_val); }
      )?
      { $expr = std::make_unique<cql3::statements::create_aggregate_statement>(std::move(fn), std::move(arg_types), std::move(sfunc), std::move(stype), std::move(rfunc), std::move(ffunc), std::move(ival), or_replace, if_not_exists); }
    ;

createFunctionStatement returns [std::unique_ptr<cql3::statements::create_function_statement> expr]
    @init {
        bool or_replace = false;
        bool if_not_exists = false;

        std::vector<shared_ptr<cql3::column_identifier>> arg_names;
        std::vector<shared_ptr<cql3_type::raw>> arg_types;
        bool called_on_null_input = false;
    }
    : K_CREATE
        // "OR REPLACE" and "IF NOT EXISTS" cannot be used together
        ((K_OR K_REPLACE { or_replace = true; } K_FUNCTION)
         | (K_FUNCTION K_IF K_NOT K_EXISTS { if_not_exists = true; })
         | K_FUNCTION)
      fn=functionName
      '('
        (
          k=ident v=comparatorType { arg_names.push_back(k); arg_types.push_back(v); }
          ( ',' k=ident v=comparatorType { arg_names.push_back(k); arg_types.push_back(v); } )*
        )?
      ')'
      ( (K_RETURNS K_NULL) | (K_CALLED { called_on_null_input = true; })) K_ON K_NULL K_INPUT
      K_RETURNS rt = comparatorType
      K_LANGUAGE language = IDENT
      K_AS body = STRING_LITERAL
      { $expr = std::make_unique<cql3::statements::create_function_statement>(std::move(fn), to_lower($language.text), $body.text, std::move(arg_names), std::move(arg_types), std::move(rt), called_on_null_input, or_replace, if_not_exists); }
    ;

dropFunctionStatement returns [std::unique_ptr<cql3::statements::drop_function_statement> expr]
    @init {
        bool if_exists = false;
        std::vector<shared_ptr<cql3_type::raw>> arg_types;
        bool args_present = false;
    }
    : K_DROP K_FUNCTION
      (K_IF K_EXISTS { if_exists = true; } )?
      fn=functionName
      (
        '('
          (
            v=comparatorType { arg_types.push_back(v); }
            ( ',' v=comparatorType { arg_types.push_back(v); } )*
          )?
        ')'
        { args_present = true; }
      )?
      { $expr = std::make_unique<cql3::statements::drop_function_statement>(std::move(fn), std::move(arg_types), args_present, if_exists); }
    ;

/**
 * CREATE KEYSPACE [IF NOT EXISTS] <KEYSPACE> WITH attr1 = value1 AND attr2 = value2;
 */
createKeyspaceStatement returns [std::unique_ptr<cql3::statements::create_keyspace_statement> expr]
    @init {
        auto attrs = make_shared<cql3::statements::ks_prop_defs>();
        bool if_not_exists = false;
    }
    : K_CREATE K_KEYSPACE (K_IF K_NOT K_EXISTS { if_not_exists = true; } )? ks=keyspaceName
      K_WITH properties[*attrs] { $expr = std::make_unique<cql3::statements::create_keyspace_statement>(ks, attrs, if_not_exists); }
    ;

/**
 * CREATE COLUMNFAMILY [IF NOT EXISTS] <CF> (
 *     <name1> <type>,
 *     <name2> <type>,
 *     <name3> <type>
 * ) WITH <property> = <value> AND ...;
 */
createTableStatement returns [std::unique_ptr<cql3::statements::create_table_statement::raw_statement> expr]
    @init { bool if_not_exists = false; }
    : K_CREATE K_COLUMNFAMILY (K_IF K_NOT K_EXISTS { if_not_exists = true; } )?
      cf=columnFamilyName { $expr = std::make_unique<cql3::statements::create_table_statement::raw_statement>(cf, if_not_exists); }
      cfamDefinition[*expr]
    ;

cfamDefinition[cql3::statements::create_table_statement::raw_statement& expr]
    : '(' cfamColumns[expr] ( ',' cfamColumns[expr]? )* ')'
      ( K_WITH cfamProperty[$expr.properties()] ( K_AND cfamProperty[$expr.properties()] )*)?
    ;

cfamColumns[cql3::statements::create_table_statement::raw_statement& expr]
    @init { bool is_static=false; }
    : k=ident v=comparatorType (K_STATIC {is_static = true;})? { $expr.add_definition(k, v, is_static); }
        (K_PRIMARY K_KEY { $expr.add_key_aliases(std::vector<shared_ptr<cql3::column_identifier>>{k}); })?
    | K_PRIMARY K_KEY '(' pkDef[expr] (',' c=ident { $expr.add_column_alias(c); } )* ')'
    ;

pkDef[cql3::statements::create_table_statement::raw_statement& expr]
    @init { std::vector<shared_ptr<cql3::column_identifier>> l; }
    : k=ident { $expr.add_key_aliases(std::vector<shared_ptr<cql3::column_identifier>>{k}); }
    | '(' k1=ident { l.push_back(k1); } ( ',' kn=ident { l.push_back(kn); } )* ')' { $expr.add_key_aliases(l); }
    ;

cfamProperty[cql3::statements::cf_properties& expr]
    : property[*$expr.properties()]
    | K_COMPACT K_STORAGE { $expr.set_compact_storage(); }
    | K_CLUSTERING K_ORDER K_BY '(' cfamOrdering[expr] (',' cfamOrdering[expr])* ')'
    ;

cfamOrdering[cql3::statements::cf_properties& expr]
    @init{ bool reversed=false; }
    : k=ident (K_ASC | K_DESC { reversed=true;} ) { $expr.set_ordering(k, reversed); }
    ;


/**
 * CREATE TYPE foo (
 *    <name1> <type1>,
 *    <name2> <type2>,
 *    ....
 * )
 */
createTypeStatement returns [std::unique_ptr<create_type_statement> expr]
    @init { bool if_not_exists = false; }
    : K_CREATE K_TYPE (K_IF K_NOT K_EXISTS { if_not_exists = true; } )?
         tn=userTypeName { $expr = std::make_unique<create_type_statement>(ut_name(std::move(tn)), if_not_exists); }
         '(' typeColumns[*expr] ( ',' typeColumns[*expr]? )* ')'
    ;

typeColumns[create_type_statement& expr]
    : k=ident v=comparatorType { $expr.add_definition(k, v); }
    ;


/**
 * CREATE INDEX [IF NOT EXISTS] [indexName] ON <columnFamily> (<columnName>);
 * CREATE CUSTOM INDEX [IF NOT EXISTS] [indexName] ON <columnFamily> (<columnName>) USING <indexClass>;
 */
createIndexStatement returns [std::unique_ptr<create_index_statement> expr]
    @init {
        auto props = make_shared<index_prop_defs>();
        bool if_not_exists = false;
        auto name = ::make_shared<cql3::index_name>();
        std::vector<::shared_ptr<index_target::raw>> targets;
    }
    : K_CREATE (K_CUSTOM { props->is_custom = true; })? K_INDEX (K_IF K_NOT K_EXISTS { if_not_exists = true; } )?
        (idxName[*name])? K_ON cf=columnFamilyName '(' (target1=indexIdent { targets.emplace_back(target1); } (',' target2=indexIdent { targets.emplace_back(target2); } )*)? ')'
        (K_USING cls=STRING_LITERAL { props->custom_class = sstring{$cls.text}; })?
        (K_WITH properties[*props])?
      { $expr = std::make_unique<create_index_statement>(cf, name, targets, props, if_not_exists); }
    ;

indexIdent returns [::shared_ptr<index_target::raw> id]
    @init {
        std::vector<::shared_ptr<cql3::column_identifier::raw>> columns;
    }
    : c=cident                   { $id = index_target::raw::regular_values_of(c); }
    | K_VALUES '(' c=cident ')'  { $id = index_target::raw::collection_values_of(c); }
    | K_KEYS '(' c=cident ')'    { $id = index_target::raw::keys_of(c); }
    | K_ENTRIES '(' c=cident ')' { $id = index_target::raw::keys_and_values_of(c); }
    | K_FULL '(' c=cident ')'    { $id = index_target::raw::full_collection(c); }
    | '(' c1=cident { columns.push_back(c1); } ( ',' cn=cident { columns.push_back(cn); } )* ')' { $id = index_target::raw::columns(std::move(columns)); }

    ;

/**
 * CREATE MATERIALIZED VIEW <viewName> AS
 *  SELECT <columns>
 *  FROM <CF>
 *  WHERE <pkColumns> IS NOT NULL
 *  PRIMARY KEY (<pkColumns>)
 *  WITH <property> = <value> AND ...;
 */
createViewStatement returns [std::unique_ptr<create_view_statement> expr]
    @init {
        bool if_not_exists = false;
        std::vector<::shared_ptr<cql3::column_identifier::raw>> partition_keys;
        std::vector<::shared_ptr<cql3::column_identifier::raw>> composite_keys;
        expression wclause = conjunction{};
    }
    : K_CREATE K_MATERIALIZED K_VIEW (K_IF K_NOT K_EXISTS { if_not_exists = true; })? cf=columnFamilyName K_AS
        K_SELECT sclause=selectClause K_FROM basecf=columnFamilyName
        (K_WHERE w=whereClause { wclause = std::move(w); } )?
        K_PRIMARY K_KEY (
        '(' '(' k1=cident { partition_keys.push_back(k1); } ( ',' kn=cident { partition_keys.push_back(kn); } )* ')' ( ',' c1=cident { composite_keys.push_back(c1); } )* ')'
    |   '(' k1=cident { partition_keys.push_back(k1); } ( ',' cn=cident { composite_keys.push_back(cn); } )* ')'
        )
        {
             $expr = std::make_unique<create_view_statement>(
                std::move(cf),
                std::move(basecf),
                std::move(sclause),
                std::move(wclause),
                std::move(partition_keys),
                std::move(composite_keys),
                if_not_exists);
        }
        ( K_WITH cfamProperty[{ $expr->properties() }] ( K_AND cfamProperty[{ $expr->properties() }] )*)?
    ;

#if 0
/**
 * CREATE TRIGGER triggerName ON columnFamily USING 'triggerClass';
 */
createTriggerStatement returns [CreateTriggerStatement expr]
    @init {
        boolean ifNotExists = false;
    }
    : K_CREATE K_TRIGGER (K_IF K_NOT K_EXISTS { ifNotExists = true; } )? (name=cident)
        K_ON cf=columnFamilyName K_USING cls=STRING_LITERAL
      { $expr = new CreateTriggerStatement(cf, name.toString(), $cls.text, ifNotExists); }
    ;

/**
 * DROP TRIGGER [IF EXISTS] triggerName ON columnFamily;
 */
dropTriggerStatement returns [DropTriggerStatement expr]
     @init { boolean ifExists = false; }
    : K_DROP K_TRIGGER (K_IF K_EXISTS { ifExists = true; } )? (name=cident) K_ON cf=columnFamilyName
      { $expr = new DropTriggerStatement(cf, name.toString(), ifExists); }
    ;

#endif

/**
 * ALTER KEYSPACE <KS> WITH <property> = <value>;
 */
alterKeyspaceStatement returns [std::unique_ptr<cql3::statements::alter_keyspace_statement> expr]
    @init {
        auto attrs = make_shared<cql3::statements::ks_prop_defs>();
    }
    : K_ALTER K_KEYSPACE ks=keyspaceName
        K_WITH properties[*attrs] { $expr = std::make_unique<cql3::statements::alter_keyspace_statement>(ks, attrs); }
    ;

/**
 * ALTER COLUMN FAMILY <CF> ALTER <column> TYPE <newtype>;
 * ALTER COLUMN FAMILY <CF> ADD <column> <newtype>; | ALTER COLUMN FAMILY <CF> ADD (<column> <newtype>,<column1> <newtype1>..... <column n> <newtype n>)
 * ALTER COLUMN FAMILY <CF> DROP <column> [USING TIMESTAMP <ts>]; | ALTER COLUMN FAMILY <CF> DROP ( <column>,<column1>.....<column n>) [USING TIMESTAMP <ts>]
 * ALTER COLUMN FAMILY <CF> WITH <property> = <value>;
 * ALTER COLUMN FAMILY <CF> RENAME <column> TO <column>;
 */
alterTableStatement returns [std::unique_ptr<alter_table_statement::raw_statement> expr]
    @init {
        alter_table_statement::type type;
        auto props = cql3::statements::cf_prop_defs();
        std::vector<alter_table_statement::column_change> column_changes;
        std::vector<std::pair<shared_ptr<cql3::column_identifier::raw>, shared_ptr<cql3::column_identifier::raw>>> renames;
        auto attrs = std::make_unique<cql3::attributes::raw>();
    }
    : K_ALTER K_COLUMNFAMILY cf=columnFamilyName
          ( K_ALTER id=cident K_TYPE v=comparatorType { type = alter_table_statement::type::alter; column_changes.emplace_back(alter_table_statement::column_change{id, v}); }
          | K_ADD                                     { type = alter_table_statement::type::add; }
            (          id=cident   v=comparatorType   s=cfisStatic { column_changes.emplace_back(alter_table_statement::column_change{id,  v,  s});  }
            | '('     id1=cident  v1=comparatorType  s1=cfisStatic { column_changes.emplace_back(alter_table_statement::column_change{id1, v1, s1}); }
                 (',' idn=cident  vn=comparatorType  sn=cfisStatic { column_changes.emplace_back(alter_table_statement::column_change{idn, vn, sn}); } )* ')'
            )
          | K_DROP                                    { type = alter_table_statement::type::drop; }
            (          id=cident { column_changes.emplace_back(alter_table_statement::column_change{id});  }
            | '('     id1=cident { column_changes.emplace_back(alter_table_statement::column_change{id1}); }
                 (',' idn=cident { column_changes.emplace_back(alter_table_statement::column_change{idn}); } )* ')'
            )
            ( usingTimestampClause[attrs] )?
          | K_WITH  properties[props]                 { type = alter_table_statement::type::opts; }
          | K_RENAME                                  { type = alter_table_statement::type::rename; }
               id1=cident K_TO toId1=cident { renames.emplace_back(id1, toId1); }
               ( K_AND idn=cident K_TO toIdn=cident { renames.emplace_back(idn, toIdn); } )*
          )
    {
        $expr = std::make_unique<alter_table_statement::raw_statement>(std::move(cf), type, std::move(column_changes), std::move(props), std::move(renames), std::move(attrs));
    }
    ;

cfisStatic returns [bool isStaticColumn=false]
    : (K_STATIC { $isStaticColumn=true; })?
    ;

/**
 * ALTER TYPE <name> ALTER <field> TYPE <newtype>;
 * ALTER TYPE <name> ADD <field> <newtype>;
 * ALTER TYPE <name> RENAME <field> TO <newtype> AND ...;
 */
alterTypeStatement returns [std::unique_ptr<alter_type_statement> expr]
    : K_ALTER K_TYPE name=userTypeName
          ( K_ALTER f=ident K_TYPE v=comparatorType { $expr = std::make_unique<alter_type_statement::add_or_alter>(std::move(name), false, f, v); }
          | K_ADD   f=ident v=comparatorType        { $expr = std::make_unique<alter_type_statement::add_or_alter>(std::move(name), true, f, v); }
          | K_RENAME
               { $expr = std::make_unique<alter_type_statement::renames>(std::move(name)); }
               renames[{ static_cast<alter_type_statement::renames&>(*$expr) }]
          )
    ;

/**
 * ALTER MATERIALIZED VIEW <CF> WITH <property> = <value>;
 */
alterViewStatement returns [std::unique_ptr<alter_view_statement> expr]
    @init {
        auto props = cql3::statements::cf_prop_defs();
    }
    : K_ALTER K_MATERIALIZED K_VIEW cf=columnFamilyName K_WITH properties[props]
    {
        $expr = std::make_unique<alter_view_statement>(std::move(cf), std::move(props));
    }
    ;

renames[alter_type_statement::renames& expr]
    : fromId=ident K_TO toId=ident { $expr.add_rename(fromId, toId); }
      ( K_AND renames[$expr] )?
    ;

/**
 * DROP KEYSPACE [IF EXISTS] <KSP>;
 */
dropKeyspaceStatement returns [std::unique_ptr<drop_keyspace_statement> ksp]
    @init { bool if_exists = false; }
    : K_DROP K_KEYSPACE (K_IF K_EXISTS { if_exists = true; } )? ks=keyspaceName { $ksp = std::make_unique<drop_keyspace_statement>(ks, if_exists); }
    ;

/**
 * DROP COLUMNFAMILY [IF EXISTS] <CF>;
 */
dropTableStatement returns [std::unique_ptr<drop_table_statement> stmt]
    @init { bool if_exists = false; }
    : K_DROP K_COLUMNFAMILY (K_IF K_EXISTS { if_exists = true; } )? cf=columnFamilyName { $stmt = std::make_unique<drop_table_statement>(cf, if_exists); }
    ;

/**
 * DROP TYPE <name>;
 */
dropTypeStatement returns [std::unique_ptr<drop_type_statement> stmt]
    @init { bool if_exists = false; }
    : K_DROP K_TYPE (K_IF K_EXISTS { if_exists = true; } )? name=userTypeName { $stmt = std::make_unique<drop_type_statement>(std::move(name), if_exists); }
    ;

/**
 * DROP MATERIALIZED VIEW [IF EXISTS] <view_name>
 */
dropViewStatement returns [std::unique_ptr<drop_view_statement> stmt]
    @init { bool if_exists = false; }
    : K_DROP K_MATERIALIZED K_VIEW (K_IF K_EXISTS { if_exists = true; } )? cf=columnFamilyName
      { $stmt = std::make_unique<drop_view_statement>(cf, if_exists); }
    ;

/**
 * DROP INDEX [IF EXISTS] <INDEX_NAME>
 */
dropIndexStatement returns [std::unique_ptr<drop_index_statement> expr]
    @init { bool if_exists = false; }
    : K_DROP K_INDEX (K_IF K_EXISTS { if_exists = true; } )? index=indexName
      { $expr = std::make_unique<drop_index_statement>(index, if_exists); }
    ;

/**
  * TRUNCATE [TABLE] <CF>
  * [USING TIMEOUT <duration>];
  */
truncateStatement returns [std::unique_ptr<raw::truncate_statement> stmt]
    @init {
        auto attrs = std::make_unique<cql3::attributes::raw>();
    }
    : K_TRUNCATE (K_COLUMNFAMILY)? cf=columnFamilyName
      ( usingTimeoutClause[attrs] )?
      {
        $stmt = std::make_unique<raw::truncate_statement>(std::move(cf), std::move(attrs));
      }
    ;

/**
 * GRANT <permission> ON <resource> TO <grantee>
 */
grantStatement returns [std::unique_ptr<grant_statement> stmt]
    : K_GRANT
          permissionOrAll
      K_ON
          r=resource
      K_TO
          grantee=userOrRoleName
      { $stmt = std::make_unique<grant_statement>($permissionOrAll.perms, std::move(r), std::move(grantee)); }
    ;

/**
 * REVOKE <permission> ON <resource> FROM <revokee>
 */
revokeStatement returns [std::unique_ptr<revoke_statement> stmt]
    : K_REVOKE
          permissionOrAll
      K_ON
          r=resource
      K_FROM
          revokee=userOrRoleName
      { $stmt = std::make_unique<revoke_statement>($permissionOrAll.perms, std::move(r), std::move(revokee)); }
    ;

/**
 * GRANT <rolename> to <grantee>
 */
grantRoleStatement returns [std::unique_ptr<grant_role_statement> stmt]
    : K_GRANT role=userOrRoleName K_TO grantee=userOrRoleName
      { $stmt = std::make_unique<grant_role_statement>(std::move(role), std::move(grantee));  }
    ;

/**
 * REVOKE <rolename> FROM <revokee>
 */
revokeRoleStatement returns [std::unique_ptr<revoke_role_statement> stmt]
    : K_REVOKE role=userOrRoleName K_FROM revokee=userOrRoleName
      { $stmt = std::make_unique<revoke_role_statement>(std::move(role), std::move(revokee)); }
    ;

listPermissionsStatement returns [std::unique_ptr<list_permissions_statement> stmt]
    @init {
		std::optional<auth::resource> r;
		std::optional<sstring> role;
		bool recursive = true;
    }
    : K_LIST
          permissionOrAll
      ( K_ON rr=resource { r = std::move(rr); } )?
      ( K_OF rn=userOrRoleName { role = sstring(cql3::role_name(std::move(rn)).to_string()); } )?
      ( K_NORECURSIVE { recursive = false; } )?
      { $stmt = std::make_unique<list_permissions_statement>($permissionOrAll.perms, std::move(r), std::move(role), recursive); }
    ;

permission returns [auth::permission perm = auth::permission{}]
    : p=(K_CREATE | K_ALTER | K_DROP | K_SELECT | K_MODIFY | K_AUTHORIZE | K_DESCRIBE | K_EXECUTE)
    { $perm = auth::permissions::from_string($p.text); }
    ;

permissionOrAll returns [auth::permission_set perms]
    : K_ALL ( K_PERMISSIONS )?       { $perms = auth::permissions::ALL; }
    | p=permission ( K_PERMISSION )? { $perms = auth::permission_set::from_mask(auth::permission_set::mask_for($p.perm)); }
    ;

resource returns [uninitialized<auth::resource> res]
    : d=dataResource     { $res = std::move(d); }
    | r=roleResource     { $res = std::move(r); }
    | f=functionResource { $res = std::move(f); }
    ;

dataResource returns [uninitialized<auth::resource> res]
    : K_ALL K_KEYSPACES { $res = auth::resource(auth::resource_kind::data); }
    | K_KEYSPACE ks = keyspaceName { $res = auth::make_data_resource($ks.id); }
    | ( K_COLUMNFAMILY )? cf = columnFamilyName
      { $res = auth::make_data_resource($cf.name.has_keyspace() ? $cf.name.get_keyspace() : "", $cf.name.get_column_family()); }
    ;

roleResource returns [uninitialized<auth::resource> res]
    : K_ALL K_ROLES { $res = auth::resource(auth::resource_kind::role); }
    | K_ROLE role = userOrRoleName { $res = auth::make_role_resource(cql3::role_name(std::move(role)).to_string()); }
    ;

functionResource returns [uninitialized<auth::resource> res]
    @init {
        std::vector<shared_ptr<cql3_type::raw>> args_types;
    }
    : K_ALL K_FUNCTIONS { $res = auth::make_functions_resource(); }
    | K_ALL K_FUNCTIONS K_IN K_KEYSPACE ks = keyspaceName { $res = auth::make_functions_resource($ks.id); }
    | K_FUNCTION fn=functionName
      (
        '('
          (
            v=comparatorType { args_types.push_back(v); }
            ( ',' v=comparatorType { args_types.push_back(v); } )*
          )?
        ')'
      )
      { $res = auth::make_functions_resource($fn.s.keyspace, $fn.s.name, args_types); }
    ;

/**
 * CREATE USER [IF NOT EXISTS] <username> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
 */
createUserStatement returns [std::unique_ptr<create_role_statement> stmt]
    @init {
        cql3::role_options opts;
        opts.is_superuser = false;
        opts.can_login = true;

        bool ifNotExists = false;
    }
    : K_CREATE K_USER (K_IF K_NOT K_EXISTS { ifNotExists = true; })? u=username
      ( K_WITH K_PASSWORD v=STRING_LITERAL { opts.password = $v.text; })?
      ( K_SUPERUSER { opts.is_superuser = true; } | K_NOSUPERUSER { opts.is_superuser = false; } )?
      { $stmt = std::make_unique<create_role_statement>(cql3::role_name(u, cql3::preserve_role_case::yes), std::move(opts), ifNotExists); }
    ;

/**
 * ALTER USER <username> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
 */
alterUserStatement returns [std::unique_ptr<alter_role_statement> stmt]
    @init {
        cql3::role_options opts;
    }
    : K_ALTER K_USER u=username
      ( K_WITH K_PASSWORD v=STRING_LITERAL { opts.password = $v.text; })?
      ( K_SUPERUSER { opts.is_superuser = true; } | K_NOSUPERUSER { opts.is_superuser = false; } )?
      { $stmt = std::make_unique<alter_role_statement>(cql3::role_name(u, cql3::preserve_role_case::yes), std::move(opts)); }
    ;

/**
 * DROP USER [IF EXISTS] <username>
 */
dropUserStatement returns [std::unique_ptr<drop_role_statement> stmt]
    @init { bool ifExists = false; }
    : K_DROP K_USER (K_IF K_EXISTS { ifExists = true; })? u=username
      { $stmt = std::make_unique<drop_role_statement>(cql3::role_name(std::move(u), cql3::preserve_role_case::yes), ifExists); }
    ;

/**
 * LIST USERS
 */
listUsersStatement returns [std::unique_ptr<list_users_statement> stmt]
    : K_LIST K_USERS { $stmt = std::make_unique<list_users_statement>(); }
    ;

/**
 * CREATE ROLE [IF NOT EXISTS] <role_name> [WITH <roleOption> [AND <roleOption>]*]
 */
createRoleStatement returns [std::unique_ptr<create_role_statement> stmt]
    @init {
        cql3::role_options opts;
        opts.is_superuser = false;
        opts.can_login = false;
        bool if_not_exists = false;
    }
    : K_CREATE K_ROLE (K_IF K_NOT K_EXISTS { if_not_exists = true; })? name=userOrRoleName
      (K_WITH roleOptions[opts])?
      { $stmt = std::make_unique<create_role_statement>(std::move(name), std::move(opts), if_not_exists); }
    ;

/**
 * ALTER ROLE <rolename> [WITH <roleOption> [AND <roleOption>]*]
 */
alterRoleStatement returns [std::unique_ptr<alter_role_statement> stmt]
    @init {
        cql3::role_options opts;
    }
    : K_ALTER K_ROLE name=userOrRoleName
      (K_WITH roleOptions[opts])?
      { $stmt = std::make_unique<alter_role_statement>(std::move(name), std::move(opts)); }
    ;

/**
 * DROP ROLE [IF EXISTS] <rolename>
 */
dropRoleStatement returns [std::unique_ptr<drop_role_statement> stmt]
    @init {
        bool if_exists = false;
    }
    : K_DROP K_ROLE (K_IF K_EXISTS { if_exists = true; })? name=userOrRoleName
      { $stmt = std::make_unique<drop_role_statement>(std::move(name), if_exists); }
    ;

/**
 * LIST ROLES [OF <rolename>] [NORECURSIVE]
 */
listRolesStatement returns [std::unique_ptr<list_roles_statement> stmt]
    @init {
        bool recursive = true;
        std::optional<cql3::role_name> grantee;
    }
    : K_LIST K_ROLES
        (K_OF g=userOrRoleName { grantee = std::move(g); })?
        (K_NORECURSIVE { recursive = false; })?
        { $stmt = std::make_unique<list_roles_statement>(grantee, recursive); }
    ;

roleOptions[cql3::role_options& opts]
    : roleOption[opts] (K_AND roleOption[opts])*
    ;

roleOption[cql3::role_options& opts]
    : K_PASSWORD '=' v=STRING_LITERAL { opts.password = $v.text; }
    | K_HASHED K_PASSWORD '=' v=STRING_LITERAL { opts.hashed_password = $v.text; }
    | K_OPTIONS '=' m=mapLiteral { opts.options = convert_property_map(m); }
    | K_SUPERUSER '=' b=BOOLEAN { opts.is_superuser = convert_boolean_literal($b.text); }
    | K_LOGIN '=' b=BOOLEAN { opts.can_login = convert_boolean_literal($b.text); }
    ;

// Introduce a more natural syntax (SERVICE LEVEL), but still allow
// the original one (SERVICE_LEVEL)
serviceLevel
    : K_SERVICE_LEVEL | ( K_SERVICE K_LEVEL )
    ;
serviceLevels
    : K_SERVICE_LEVELS | ( K_SERVICE K_LEVELS )
    ;
/**
 * CREATE SERVICE_LEVEL [IF NOT EXISTS] <service_level_name> [WITH <param> = <value>]
 */
createServiceLevelStatement returns [std::unique_ptr<create_service_level_statement> stmt]
    @init {
        auto attrs = make_shared<cql3::statements::sl_prop_defs>();
        bool if_not_exists = false;
    }
    : K_CREATE serviceLevel (K_IF K_NOT K_EXISTS { if_not_exists = true; })? name=serviceLevelOrRoleName (K_WITH properties[*attrs])?
      { $stmt = std::make_unique<create_service_level_statement>(name, attrs, if_not_exists); }
    ;

/**
 * ALTER SERVICE_LEVEL <service_level_name> WITH <param> = <value>
 */
alterServiceLevelStatement returns [std::unique_ptr<alter_service_level_statement> stmt]
    @init {
        auto attrs = make_shared<cql3::statements::sl_prop_defs>();
    }
    : K_ALTER serviceLevel name=serviceLevelOrRoleName K_WITH properties[*attrs]
      { $stmt = std::make_unique<alter_service_level_statement>(name, attrs); }
    ;

/**
 * DROP SERVICE_LEVEL [IF EXISTS] <service_level_name>
 */
dropServiceLevelStatement returns [std::unique_ptr<drop_service_level_statement> stmt]
    @init {
        bool if_exists = false;
    }
    : K_DROP serviceLevel (K_IF K_EXISTS { if_exists = true; })? name=serviceLevelOrRoleName
      { $stmt = std::make_unique<drop_service_level_statement>(name, if_exists); }
    ;

/**
 * ATTACH SERVICE_LEVEL <service_level_name> TO <role_name>
 */
attachServiceLevelStatement returns [std::unique_ptr<attach_service_level_statement> stmt]
    @init {
    }
    : K_ATTACH serviceLevel service_level_name=serviceLevelOrRoleName K_TO role_name=serviceLevelOrRoleName
      { $stmt = std::make_unique<attach_service_level_statement>(service_level_name, role_name); }
    ;

/**
 * DETACH SERVICE_LEVEL FROM <role_name>
 */
detachServiceLevelStatement returns [std::unique_ptr<detach_service_level_statement> stmt]
    @init {
    }
    : K_DETACH serviceLevel K_FROM role_name=serviceLevelOrRoleName
      { $stmt = std::make_unique<detach_service_level_statement>(role_name); }
    ;


/**
 * LIST SERVICE_LEVEL <service_level_name>
 * LIST ALL SERVICE_LEVELS
 */
listServiceLevelStatement returns [std::unique_ptr<list_service_level_statement> stmt]
    @init {
    }
    : K_LIST serviceLevel service_level_name=serviceLevelOrRoleName
      { $stmt = std::make_unique<list_service_level_statement>(service_level_name, false); } |
      K_LIST K_ALL serviceLevels
      { $stmt = std::make_unique<list_service_level_statement>("", true); }
    ;

/**
 * LIST ATTACHED SERVICE_LEVEL OF <role_name>
 * LIST ALL ATTACHED SERVICE_LEVELS
 */
listServiceLevelAttachStatement returns [std::unique_ptr<list_service_level_attachments_statement> stmt]
    @init {
        bool allow_nonexisting_roles = false;
    }
    : K_LIST K_ATTACHED serviceLevel K_OF role_name=serviceLevelOrRoleName
      { $stmt = std::make_unique<list_service_level_attachments_statement>(role_name); } |
      K_LIST K_ALL K_ATTACHED serviceLevels
      { $stmt = std::make_unique<list_service_level_attachments_statement>(); }
    ;

/**
 * LIST EFFECTIVE SERVICE_LEVEL OF <role_name>
 */
listEffectiveServiceLevelStatement returns [std::unique_ptr<list_effective_service_level_statement stmt>]
    @init {
    }
    : K_LIST K_EFFECTIVE serviceLevel K_OF role_name=serviceLevelOrRoleName
      { $stmt = std::make_unique<list_effective_service_level_statement>(role_name); }
    ;

/**
 * (DESCRIBE | DESC) (
 *    CLUSTER
 *    [FULL] SCHEMA
 *    KEYSPACES
 *    [ONLY] KEYSPACE <name>?
 *    TABLES
 *    TABLE <name>
 *    TYPES
 *    TYPE <name>
 *    FUNCTIONS
 *    FUNCTION <name>
 *    AGGREGATES
 *    AGGREGATE <name>
 * ) (WITH INTERNALS)?
 */
describeStatement returns [std::unique_ptr<cql3::statements::raw::describe_statement> stmt]
    @init {
        bool fullSchema = false;
        bool pending = false;
        bool config = false;
        bool only = false;
        std::optional<sstring> keyspace;
        sstring generic_name = "";
        bool with_hashed_passwords = false;
    }
    : ( K_DESCRIBE | K_DESC )
    ( (K_CLUSTER) => K_CLUSTER                      { $stmt = cql3::statements::raw::describe_statement::cluster();                }
    | (K_FULL { fullSchema=true; })? K_SCHEMA       { $stmt = cql3::statements::raw::describe_statement::schema(fullSchema);       }
    | (K_KEYSPACES) => K_KEYSPACES                  { $stmt = cql3::statements::raw::describe_statement::keyspaces();              }
    | (K_ONLY { only=true; })? K_KEYSPACE ( ks=keyspaceName { keyspace = ks; })?
                                                    { $stmt = cql3::statements::raw::describe_statement::keyspace(keyspace, only); }
    | (K_TABLES) => K_TABLES                        { $stmt = cql3::statements::raw::describe_statement::tables();                 }
    | K_COLUMNFAMILY cf=columnFamilyName            { $stmt = cql3::statements::raw::describe_statement::table(cf);                }
    | K_INDEX idx=columnFamilyName                  { $stmt = cql3::statements::raw::describe_statement::index(idx);               }
    | K_MATERIALIZED K_VIEW view=columnFamilyName   { $stmt = cql3::statements::raw::describe_statement::view(view);               }
    | (K_TYPES) => K_TYPES                          { $stmt = cql3::statements::raw::describe_statement::types();                  }
    | K_TYPE tn=userTypeName                        { $stmt = cql3::statements::raw::describe_statement::type(std::move(tn));      }
    | (K_FUNCTIONS) => K_FUNCTIONS                  { $stmt = cql3::statements::raw::describe_statement::functions();              }
    | K_FUNCTION fn=functionName                    { $stmt = cql3::statements::raw::describe_statement::function(fn);             }
    | (K_AGGREGATES) => K_AGGREGATES                { $stmt = cql3::statements::raw::describe_statement::aggregates();             }
    | K_AGGREGATE ag=functionName                   { $stmt = cql3::statements::raw::describe_statement::aggregate(ag);            }
    | ( ( ksT=IDENT                                 { keyspace = sstring{$ksT.text}; }
        | ksT=QUOTED_NAME                           { keyspace = sstring{$ksT.text}; }
        | ksK=unreserved_keyword                    { keyspace = ksK; } ) 
        '.' )?
        ( tT=IDENT                                  { generic_name = sstring{$tT.text}; }
        | tT=QUOTED_NAME                            { generic_name = sstring{$tT.text}; }
        | tK=unreserved_keyword                     { generic_name = tK; } )
                                                    { $stmt = cql3::statements::raw::describe_statement::generic(keyspace, generic_name); }
    )
    ( K_WITH K_INTERNALS (K_AND K_PASSWORDS { with_hashed_passwords = true; })?
        { $stmt->with_internals_details(with_hashed_passwords); } )?
    ;

/** DEFINITIONS **/

// Column Identifiers.  These need to be treated differently from other
// identifiers because the underlying comparator is not necessarily text. See
// CASSANDRA-8178 for details.
cident returns [shared_ptr<cql3::column_identifier::raw> id]
    : t=IDENT              { $id = ::make_shared<cql3::column_identifier::raw>(sstring{$t.text}, false); }
    | t=QUOTED_NAME        { $id = ::make_shared<cql3::column_identifier::raw>(sstring{$t.text}, true); }
    | k=unreserved_keyword { $id = ::make_shared<cql3::column_identifier::raw>(k, false); }
    ;

// Identifiers that do not refer to columns or where the comparator is known to be text
ident returns [shared_ptr<cql3::column_identifier> id]
    : t=IDENT              { $id = ::make_shared<cql3::column_identifier>(sstring{$t.text}, false); }
    | t=QUOTED_NAME        { $id = ::make_shared<cql3::column_identifier>(sstring{$t.text}, true); }
    | k=unreserved_keyword { $id = ::make_shared<cql3::column_identifier>(k, false); }
    ;

// Keyspace & Column family names
keyspaceName returns [sstring id]
    @init { auto name = cql3::cf_name(); }
    : ksName[name] { $id = name.get_keyspace(); }
    ;

indexName returns [::shared_ptr<cql3::index_name> name]
    @init { $name = ::make_shared<cql3::index_name>(); }
    : (ksName[*name] '.')? idxName[*name]
    ;

columnFamilyName returns [cql3::cf_name name]
    @init { $name = cql3::cf_name(); }
    : (ksName[name] '.')? cfName[name]
    ;

userTypeName returns [uninitialized<cql3::ut_name> name]
    : (ks=ident '.')? ut=non_type_ident { $name = cql3::ut_name(ks, ut); }
    ;

userOrRoleName returns [uninitialized<cql3::role_name> name]
    : t=IDENT              { $name = cql3::role_name($t.text, cql3::preserve_role_case::no); }
    | t=STRING_LITERAL     { $name = cql3::role_name($t.text, cql3::preserve_role_case::yes); }
    | t=QUOTED_NAME        { $name = cql3::role_name($t.text, cql3::preserve_role_case::yes); }
    | k=unreserved_keyword { $name = cql3::role_name(k, cql3::preserve_role_case::no); }
    | QMARK {add_recognition_error("Bind variables cannot be used for role names");}
    ;
    
serviceLevelOrRoleName returns [sstring name]
: t=IDENT              { $name = sstring($t.text);
						 std::transform($name.begin(), $name.end(), $name.begin(), ::tolower); }
| t=STRING_LITERAL     { $name = sstring($t.text); }
| t=QUOTED_NAME        { $name = sstring($t.text); }
| k=unreserved_keyword { $name = k;
						 std::transform($name.begin(), $name.end(), $name.begin(), ::tolower);}
| QMARK {add_recognition_error("Bind variables cannot be used for service levels or role names");}
;

ksName[cql3::keyspace_element_name& name]
    : t=IDENT              { $name.set_keyspace($t.text, false);}
    | t=QUOTED_NAME        { $name.set_keyspace($t.text, true);}
    | k=unreserved_keyword { $name.set_keyspace(k, false);}
    | QMARK {add_recognition_error("Bind variables cannot be used for keyspace names");}
    ;

cfName[cql3::cf_name& name]
    : t=IDENT              { $name.set_column_family($t.text, false); }
    | t=QUOTED_NAME        { $name.set_column_family($t.text, true); }
    | k=unreserved_keyword { $name.set_column_family(k, false); }
    | QMARK {add_recognition_error("Bind variables cannot be used for table names");}
    ;

idxName[cql3::index_name& name]
    : t=IDENT              { $name.set_index($t.text, false); }
    | t=QUOTED_NAME        { $name.set_index($t.text, true);}
    | k=unreserved_keyword { $name.set_index(k, false); }
    | QMARK {add_recognition_error("Bind variables cannot be used for index names");}
    ;

constant returns [untyped_constant constant]
    @init{std::string sign;}
    : t=STRING_LITERAL {
                    // This is a workaround for antlr3 not distinguishing between
                    // calling in lexer setText() with an empty string and not calling
                    // setText() at all.
                    auto text = $t.text;
                    if (text.size() == 1 && text[0] == '\xFF') {
                        text = {};
                    }
                    $constant = untyped_constant{untyped_constant::string, std::move(text)};
                }
    | t=INTEGER        { $constant = untyped_constant{untyped_constant::integer, $t.text}; }
    | t=FLOAT          { $constant = untyped_constant{untyped_constant::floating_point, $t.text}; }
    | t=BOOLEAN        { $constant = untyped_constant{untyped_constant::boolean, $t.text}; }
    | t=DURATION       { $constant = untyped_constant{untyped_constant::duration, $t.text}; }
    | t=UUID           { $constant = untyped_constant{untyped_constant::uuid, $t.text}; }
    | t=HEXNUMBER      { $constant = untyped_constant{untyped_constant::hex, $t.text}; }
    | { sign=""; } ('-' {sign = "-"; } )? t=(K_NAN | K_INFINITY) { $constant = untyped_constant{untyped_constant::floating_point, sign + $t.text}; }
    ;

mapLiteral returns [collection_constructor map]
    @init{std::vector<expression> m;}
    : '{' { }
          ( k1=term ':' v1=term { m.push_back(tuple_constructor{{std::move(k1), std::move(v1)}}); }
            ( ',' kn=term ':' vn=term { m.push_back(tuple_constructor{{std::move(kn), std::move(vn)}}); } )*  )?
      '}' { $map = collection_constructor{collection_constructor::style_type::map, std::move(m)}; }
    ;

setOrMapLiteral[uexpression t] returns [collection_constructor value]
	@init{ std::vector<expression> e; }
    : ':' v=term { e.push_back(tuple_constructor{{std::move(t), std::move(v)}}); }
          ( ',' kn=term ':' vn=term { e.push_back(tuple_constructor{{std::move(kn), std::move(vn)}}); } )*
      { $value = collection_constructor{collection_constructor::style_type::map, std::move(e)}; }
    | { e.push_back(std::move(t)); }
          ( ',' tn=term { e.push_back(std::move(tn)); } )*
      { $value = collection_constructor{collection_constructor::style_type::set, std::move(e)}; }
    ;

collectionLiteral returns [uexpression value]
	@init{ std::vector<expression> l; }
    : '['
          ( t1=term { l.push_back(std::move(t1)); } ( ',' tn=term { l.push_back(std::move(tn)); } )* )?
      ']' { $value = collection_constructor{collection_constructor::style_type::list_or_vector, std::move(l)}; }
    | '{' t=term v=setOrMapLiteral[t] { $value = std::move(v); } '}'
    // Note that we have an ambiguity between maps and set for "{}". So we force it to a set literal,
    // and deal with it later based on the type of the column (SetLiteral.java).
    | '{' '}' { $value = collection_constructor{collection_constructor::style_type::set, {}}; }
    ;

usertypeLiteral returns [uexpression ut]
    @init{ usertype_constructor::elements_map_type m; }
    @after{ $ut = usertype_constructor{std::move(m)}; }
    // We don't allow empty literals because that conflicts with sets/maps and is currently useless since we don't allow empty user types
    : '{' k1=ident ':' v1=term { m.emplace(std::move(*k1), std::move(v1)); } ( ',' kn=ident ':' vn=term { m.emplace(std::move(*kn), std::move(vn)); } )* '}'
    ;

tupleLiteral returns [uexpression tt]
    @init{ std::vector<expression> l; }
    @after{ $tt = tuple_constructor{std::move(l)}; }
    : '(' t1=term { l.push_back(std::move(t1)); } ( ',' tn=term { l.push_back(std::move(tn)); } )* ')'
    ;

value returns [uexpression value]
    : c=constant           { $value = std::move(c); }
    | l=collectionLiteral  { $value = std::move(l); }
    | u=usertypeLiteral    { $value = std::move(u); }
    | t=tupleLiteral       { $value = std::move(t); }
    | K_NULL               { $value = make_untyped_null(); }
    | e=marker             { $value = std::move(e); }
    ;

marker returns [uexpression value]
    : ':' id=ident         { $value = new_bind_variables(id); }
    | QMARK                { $value = new_bind_variables(shared_ptr<cql3::column_identifier>{}); }
    ;

intValue returns [uexpression value]
    : t=INTEGER     { $value = untyped_constant{untyped_constant::integer, $t.text}; }
    | e=marker      { $value = std::move(e); }
    ;

functionName returns [cql3::functions::function_name s]
    : (ks=keyspaceName '.')? f=allowedFunctionName   { $s.keyspace = std::move(ks); $s.name = std::move(f); }
    ;

allowedFunctionName returns [sstring s]
    : f=IDENT                       { $s = $f.text; std::transform(s.begin(), s.end(), s.begin(), ::tolower); }
    | f=QUOTED_NAME                 { $s = $f.text; }
    | u=unreserved_function_keyword { $s = u; }
    | K_TOKEN                       { $s = "token"; }
    | K_COUNT                       { $s = "count"; }
    ;

functionArgs returns [std::vector<expression> a]
    : '(' ')'
    | '(' t1=term { a.push_back(std::move(t1)); }
          ( ',' tn=term { a.push_back(std::move(tn)); } )*
       ')'
    ;

term returns [uexpression term1]
    : v=value                          { $term1 = std::move(v); }
    | f=functionName args=functionArgs { $term1 = function_call{std::move(f), std::move(args)}; }
    | '(' c=comparatorType ')' t=term  { $term1 = cast{.style = cast::cast_style::c, .arg = std::move(t), .type = c}; }
    ;

columnOperation[operations_type& operations]
    : key=cident columnOperationDifferentiator[operations, key]
    ;

columnOperationDifferentiator[operations_type& operations, ::shared_ptr<cql3::column_identifier::raw> key]
    : '=' normalColumnOperation[operations, key]
    | '[' k=term ']' collectionColumnOperation[operations, key, std::move(k), false]
    | '.' field=ident udtColumnOperation[operations, key, field]
    | '[' K_SCYLLA_TIMEUUID_LIST_INDEX '(' k=term ')' ']' collectionColumnOperation[operations, key, std::move(k), true]
    ;

normalColumnOperation[operations_type& operations, ::shared_ptr<cql3::column_identifier::raw> key]
    : t=term ('+' c=cident )?
      {
          if (!c) {
              operations.emplace_back(std::move(key), std::make_unique<cql3::operation::set_value>(std::move(t)));
          } else {
              if (*key != *c) {
                add_recognition_error("Only expressions of the form X = <value> + X are supported.");
              }
              operations.emplace_back(std::move(key), std::make_unique<cql3::operation::prepend>(std::move(t)));
          }
      }
    | c=cident sig=('+' | '-') t=term
      {
          if (*key != *c) {
              add_recognition_error("Only expressions of the form X = X " + $sig.text + "<value> are supported.");
          }
          std::unique_ptr<cql3::operation::raw_update> op;
          if ($sig.text == "+") {
              op = std::make_unique<cql3::operation::addition>(std::move(t));
          } else {
              op = std::make_unique<cql3::operation::subtraction>(std::move(t));
          }
          operations.emplace_back(std::move(key), std::move(op));
      }
    | c=cident i=INTEGER
      {
          // Note that this production *is* necessary because X = X - 3 will in fact be lexed as [ X, '=', X, INTEGER].
          if (*key != *c) {
              // We don't yet allow a '+' in front of an integer, but we could in the future really, so let's be future-proof in our error message
              add_recognition_error("Only expressions of the form X = X " + sstring($i.text[0] == '-' ? "-" : "+") + " <value> are supported.");
          }
          operations.emplace_back(std::move(key), std::make_unique<cql3::operation::addition>(untyped_constant{untyped_constant::integer, $i.text}));
      }
    | K_SCYLLA_COUNTER_SHARD_LIST '(' t=term ')'
      {
          operations.emplace_back(std::move(key), std::make_unique<cql3::operation::set_counter_value_from_tuple_list>(std::move(t)));
      }
    ;

collectionColumnOperation[operations_type& operations,
                          shared_ptr<cql3::column_identifier::raw> key,
                          expression k,
                          bool by_uuid]
    : '=' t=term
      {
          operations.emplace_back(std::move(key), std::make_unique<cql3::operation::set_element>(std::move(k), std::move(t), by_uuid));
      }
    ;

udtColumnOperation[operations_type& operations,
                   shared_ptr<cql3::column_identifier::raw> key,
                   shared_ptr<cql3::column_identifier> field]
    : '=' t=term
      {
          operations.emplace_back(std::move(key), std::make_unique<cql3::operation::set_field>(std::move(field), std::move(t)));
      }
    ;

columnRefExpr returns [uexpression e]
    : column=cident { e = unresolved_identifier{column}; }
    ;

subscriptExpr returns [uexpression e]
    : col=columnRefExpr { e = std::move(col); }
        ( '[' sub=term ']'  { e = subscript{std::move(e), std::move(sub)}; } )?
    ;

singleColumnInValuesOrMarkerExpr returns [uexpression e]
    : values=singleColumnInValues { e = collection_constructor{collection_constructor::style_type::list_or_vector, std::move(values)}; }
    | m=marker { e = std::move(m); }
    ;

columnCondition returns [uexpression e]
    // Note: we'll reject duplicates later
    : key=subscriptExpr
        ( op=relationType t=term {
                    e = binary_operator(
                            std::move(key),
                            op,
                            std::move(t));
                }
        | K_IN
            values=singleColumnInValuesOrMarkerExpr {
                    e = binary_operator(
                            std::move(key),
                            oper_t::IN,
                            std::move(values));
                }
        | K_NOT K_IN
            values=singleColumnInValuesOrMarkerExpr {
                    e = binary_operator(
                            std::move(key),
                            oper_t::NOT_IN,
                            std::move(values));
                }
        )
    ;

properties[cql3::statements::property_definitions& props]
    : property[props] (K_AND property[props])*
    ;

property[cql3::statements::property_definitions& props]
    : k=ident '=' simple=propertyValue { try { $props.add_property(k->to_string(), simple); } catch (exceptions::syntax_exception e) { add_recognition_error(e.what()); } }
    | k=ident '=' map=mapLiteral { try { $props.add_property(k->to_string(), convert_property_map(map)); } catch (exceptions::syntax_exception e) { add_recognition_error(e.what()); } }
    ;

propertyValue returns [sstring str]
    : c=constant           { $str = c.raw_text; }
    // FIXME: unreserved keywords below are indistinguishable from their string representation,
    // which might be problematic in the future. A possible solution is to use a more complicated
    // type for storing property values instead of just plain strings. For the specific case
    // of "null" it would be enough to use an optional, but for the general case it should be
    // a variant-like class which distinguishes plain string values from special keywords
    | u=unreserved_keyword { $str = u; }
    | K_NULL { $str = "null"; }
    ;

relationType returns [oper_t op = oper_t{}]
    : '='  { $op = oper_t::EQ; }
    | '<'  { $op = oper_t::LT; }
    | '<=' { $op = oper_t::LTE; }
    | '>'  { $op = oper_t::GT; }
    | '>=' { $op = oper_t::GTE; }
    | '!=' { $op = oper_t::NEQ; }
    | K_LIKE { $op = oper_t::LIKE; }
    ;

relation returns [uexpression e]
    @init{ oper_t rt; }
    : name=cident type=relationType t=term { $e = binary_operator(unresolved_identifier{std::move(name)}, type, std::move(t)); }

    | K_TOKEN l=tupleOfIdentifiers type=relationType t=term
        {
          $e = binary_operator(
            function_call{functions::function_name::native_function("token"), std::move(l.elements)},
            type,
            std::move(t));
        }
    | name=cident K_IS K_NOT K_NULL {
          $e = binary_operator(unresolved_identifier{std::move(name)}, oper_t::IS_NOT, make_untyped_null()); }
    | name=cident K_IN marker1=marker
        { $e = binary_operator(unresolved_identifier{std::move(name)}, oper_t::IN, std::move(marker1)); }
    | name=cident K_IN in_values=singleColumnInValues
        { $e = binary_operator(unresolved_identifier{std::move(name)}, oper_t::IN,
        collection_constructor {
            .style = collection_constructor::style_type::list_or_vector,
            .elements = std::move(in_values)
        }); }
    | name=cident K_NOT K_IN marker1=marker
        { $e = binary_operator(unresolved_identifier{std::move(name)}, oper_t::NOT_IN, std::move(marker1)); }
    | name=cident K_NOT K_IN in_values=singleColumnInValues
        { $e = binary_operator(unresolved_identifier{std::move(name)}, oper_t::NOT_IN,
        collection_constructor {
            .style = collection_constructor::style_type::list_or_vector,
            .elements = std::move(in_values)
        }); }
    | name=cident K_CONTAINS { rt = oper_t::CONTAINS; } (K_KEY { rt = oper_t::CONTAINS_KEY; })?
        t=term { $e = binary_operator(unresolved_identifier{std::move(name)}, rt, std::move(t)); }
    | name=cident '[' key=term ']' type=relationType t=term { $e = binary_operator(subscript{.val = unresolved_identifier{std::move(name)}, .sub = std::move(key)}, type, std::move(t)); }
    | ids=tupleOfIdentifiers
      ( K_IN
          ( '(' ')'
              {
                $e = binary_operator(
                    ids,
                    oper_t::IN,
                    collection_constructor {
                      .style = collection_constructor::style_type::list_or_vector,
                      .elements = std::vector<expression>()
                    }
                  );
              }
          | tupleInMarker=marker /* (a, b, c) IN ? */
              {
                $e = binary_operator(
                    ids,
                    oper_t::IN,
                    std::move(tupleInMarker)
                  );
              }
          | literals=tupleOfTupleLiterals /* (a, b, c) IN ((1, 2, 3), (4, 5, 6), ...) */
              {
                $e = binary_operator(
                    ids,
                    oper_t::IN,
                    collection_constructor {
                      .style = collection_constructor::style_type::list_or_vector,
                      .elements = std::move(literals)
                    }
                  );
              }
          | markers=tupleOfMarkersForTuples /* (a, b, c) IN (?, ?, ...) */
              {
                $e = binary_operator(
                    ids,
                    oper_t::IN,
                    collection_constructor {
                      .style = collection_constructor::style_type::list_or_vector,
                      .elements = std::move(markers)
                    }
                  );
              }
          )
      | type=relationType literal=tupleLiteral /* (a, b, c) > (1, 2, 3) or (a, b, c) > (?, ?, ?) */
          {
              $e = binary_operator(ids, type, std::move(literal));
          }
      | type=relationType K_SCYLLA_CLUSTERING_BOUND literal=tupleLiteral /* (a, b, c) > (1, 2, 3) or (a, b, c) > (?, ?, ?) */
          {
              $e = binary_operator(ids, type, std::move(literal), cql3::expr::comparison_order::clustering);
          }
      | type=relationType tupleMarker=marker /* (a, b, c) >= ? */
          {
              $e = binary_operator(ids, type, std::move(tupleMarker));
          }
      )
    | '(' e1=relation ')' { $e = std::move(e1); }
    ;

tupleOfIdentifiers returns [tuple_constructor tup]
    : '(' n1=cident { $tup.elements.push_back(unresolved_identifier{std::move(n1)}); } (',' ni=cident { $tup.elements.push_back(unresolved_identifier{std::move(ni)}); })* ')'
    ;

listOfIdentifiers returns [std::vector<::shared_ptr<cql3::column_identifier::raw>> ids]
    : n1=cident { $ids.push_back(n1); } (',' ni=cident { $ids.push_back(ni); })*
    ;

singleColumnInValues returns [std::vector<expression> list]
    : '(' ( t1 = term { $list.push_back(std::move(t1)); } (',' ti=term { $list.push_back(std::move(ti)); })* )? ')'
    ;

tupleOfTupleLiterals returns [std::vector<expression> literals]
    : '(' t1=tupleLiteral { $literals.emplace_back(std::move(t1)); } (',' ti=tupleLiteral { $literals.emplace_back(std::move(ti)); })* ')'
    ;

tupleOfMarkersForTuples returns [std::vector<expression> markers]
    : '(' m1=marker { $markers.emplace_back(std::move(m1)); } (',' mi=marker { $markers.emplace_back(std::move(mi)); })* ')'
    ;

// The comparator_type rule is used for users' queries (internal=false)
// and for internal calls from db::cql_type_parser::parse() (internal=true).
// The latter is used for reading schemas stored in the system tables, and
// may support additional column types that cannot be created through CQL,
// but only internally through code. Today the only such type is "empty":
// Scylla code internally creates columns with type "empty" or collections
// "empty" to represent unselected columns in materialized views.
// If a user (internal=false) tries to use "empty" as a type, it is treated -
// as do all unknown types - as an attempt to use a user-defined type, and
// we report this name is reserved (as for _reserved_type_names()).
comparator_type [bool internal] returns [shared_ptr<cql3_type::raw> t]
    : n=native_or_internal_type[internal]     { $t = cql3_type::raw::from(n); }
    | c=collection_type[internal]   { $t = c; }
    | tt=tuple_type[internal]       { $t = tt; }
    | vt=vector_type                { $t = vt; }
    | id=userTypeName   { $t = cql3::cql3_type::raw::user_type(std::move(id)); }
    | K_FROZEN '<' f=comparator_type[internal] '>'
      {
        try {
            $t = cql3::cql3_type::raw::frozen(f);
        } catch (exceptions::invalid_request_exception& e) {
            add_recognition_error(e.what());
        }
      }
#if 0
    | s=STRING_LITERAL
      {
        try {
            $t = CQL3Type.Raw.from(new CQL3Type.Custom($s.text));
        } catch (SyntaxException e) {
            addRecognitionError("Cannot parse type " + $s.text + ": " + e.getMessage());
        } catch (ConfigurationException e) {
            addRecognitionError("Error setting type " + $s.text + ": " + e.getMessage());
        }
      }
#endif
    ;

native_or_internal_type [bool internal] returns [data_type t]
    : n=native_type     { $t = n; }
    // The "internal" types, only supported when internal==true:
    | K_EMPTY   {
        if (internal) {
            $t = empty_type;
        } else {
            add_recognition_error("Invalid (reserved) user type name empty");
        }
      }
    ;

comparatorType returns [shared_ptr<cql3_type::raw> t]
    : tt=comparator_type[false]    { $t = tt; }
    ;

native_type returns [data_type t]
    : K_ASCII     { $t = ascii_type; }
    | K_BIGINT    { $t = long_type; }
    | K_BLOB      { $t = bytes_type; }
    | K_BOOLEAN   { $t = boolean_type; }
    | K_COUNTER   { $t = counter_type; }
    | K_DECIMAL   { $t = decimal_type; }
    | K_DOUBLE    { $t = double_type; }
    | K_DURATION  { $t = duration_type; }
    | K_FLOAT     { $t = float_type; }
    | K_INET      { $t = inet_addr_type; }
    | K_INT       { $t = int32_type; }
    | K_SMALLINT  { $t = short_type; }
    | K_TEXT      { $t = utf8_type; }
    | K_TIMESTAMP { $t = timestamp_type; }
    | K_TINYINT   { $t = byte_type; }
    | K_UUID      { $t = uuid_type; }
    | K_VARCHAR   { $t = utf8_type; }
    | K_VARINT    { $t = varint_type; }
    | K_TIMEUUID  { $t = timeuuid_type; }
    | K_DATE      { $t = simple_date_type; }
    | K_TIME      { $t = time_type; }
    ;

collection_type [bool internal] returns [shared_ptr<cql3::cql3_type::raw> pt]
    : K_MAP  '<' t1=comparator_type[internal] ',' t2=comparator_type[internal] '>'
        {
            // if we can't parse either t1 or t2, antlr will "recover" and we may have t1 or t2 null.
            if (t1 && t2) {
                $pt = cql3::cql3_type::raw::map(t1, t2);
            }
        }
    | K_LIST '<' t=comparator_type[internal] '>'
        { if (t) { $pt = cql3::cql3_type::raw::list(t); } }
    | K_SET  '<' t=comparator_type[internal] '>'
        { if (t) { $pt = cql3::cql3_type::raw::set(t); } }
    ;

tuple_type [bool internal] returns [shared_ptr<cql3::cql3_type::raw> t]
        @init{ std::vector<shared_ptr<cql3::cql3_type::raw>> types; }
    : K_TUPLE '<'
         t1=comparator_type[internal] { types.push_back(t1); } (',' tn=comparator_type[internal] { types.push_back(tn); })*
      '>' { $t = cql3::cql3_type::raw::tuple(std::move(types)); }
    ;

vector_type returns [shared_ptr<cql3::cql3_type::raw> pt]
    : K_VECTOR '<' t=comparator_type[false] ',' d=INTEGER '>'
        {
            if ($d.text[0] == '-')
                throw exceptions::invalid_request_exception("Vectors must have a dimension greater than 0");
            $pt = cql3::cql3_type::raw::vector(t, std::stoul($d.text));
        }
    ;

username returns [sstring str]
    : t=IDENT { $str = $t.text; }
    | t=STRING_LITERAL { $str = $t.text; }
    | s=unreserved_keyword { $str = s; }
    | QUOTED_NAME { add_recognition_error("Quoted strings are not supported for user names"); }
    ;

// Basically the same as cident, but we need to exclude existing CQL3 types
// (which for some reason are not reserved otherwise)
non_type_ident returns [shared_ptr<cql3::column_identifier> id]
    : t=IDENT                    { if (_reserved_type_names().contains($t.text)) { add_recognition_error("Invalid (reserved) user type name " + $t.text); } $id = ::make_shared<cql3::column_identifier>($t.text, false); }
    | t=QUOTED_NAME              { $id = ::make_shared<cql3::column_identifier>($t.text, true); }
    | k=basic_unreserved_keyword { $id = ::make_shared<cql3::column_identifier>(k, false); }
    | kk=K_KEY                   { $id = ::make_shared<cql3::column_identifier>($kk.text, false); }
    ;

unreserved_keyword returns [sstring str]
    : u=unreserved_function_keyword     { $str = u; }
    | k=(K_TTL | K_COUNT | K_WRITETIME | K_KEY) { $str = $k.text; }
    ;

unreserved_function_keyword returns [sstring str]
    : u=basic_unreserved_keyword { $str = u; }
    | u=type_unreserved_keyword  { $str = u; }
    ;

basic_unreserved_keyword returns [sstring str]
    : k=( K_KEYS
        | K_AS
        | K_CLUSTER
        | K_CLUSTERING
        | K_COMPACT
        | K_STORAGE
        | K_TABLES
        | K_TYPE
        | K_TYPES
        | K_VALUES
        | K_MAP
        | K_LIST
        | K_FILTERING
        | K_PERMISSION
        | K_PERMISSIONS
        | K_KEYSPACES
        | K_ALL
        | K_USER
        | K_USERS
        | K_ROLE
        | K_ROLES
        | K_SUPERUSER
        | K_NOSUPERUSER
        | K_LOGIN
        | K_NOLOGIN
        | K_OPTIONS
        | K_PASSWORD
        | K_PASSWORDS
        | K_HASHED
        | K_EXISTS
        | K_CUSTOM
        | K_TRIGGER
        | K_DISTINCT
        | K_CONTAINS
        | K_INTERNALS
        | K_STATIC
        | K_FROZEN
        | K_TUPLE
        | K_VECTOR
        | K_FUNCTION
        | K_FUNCTIONS
        | K_AGGREGATE
        | K_AGGREGATES
        | K_SFUNC
        | K_STYPE
        | K_REDUCEFUNC
        | K_FINALFUNC
        | K_INITCOND
        | K_RETURNS
        | K_LANGUAGE
        | K_CALLED
        | K_INPUT
        | K_JSON
        | K_CACHE
        | K_BYPASS
        | K_LIKE
        | K_PER
        | K_PARTITION
        | K_SERVICE_LEVEL
        | K_ATTACH
        | K_DETACH
        | K_SERVICE_LEVELS
        | K_ATTACHED
        | K_FOR
        | K_SHARES
        | K_GROUP
        | K_TIMEOUT
        | K_SERVICE
        | K_LEVEL
        | K_LEVELS
        | K_PRUNE
        | K_ONLY
        | K_DESCRIBE
        | K_DESC
        | K_EXECUTE
        | K_MUTATION_FRAGMENTS
        | K_EFFECTIVE
        ) { $str = $k.text; }
    ;

type_unreserved_keyword returns [sstring str]
    : k=( K_ASCII
        | K_BIGINT
        | K_BLOB
        | K_BOOLEAN
        | K_COUNTER
        | K_DECIMAL
        | K_DOUBLE
        | K_DURATION
        | K_FLOAT
        | K_INET
        | K_INT
        | K_SMALLINT
        | K_TEXT
        | K_TIMESTAMP
        | K_TINYINT
        | K_UUID
        | K_VARCHAR
        | K_VARINT
        | K_TIMEUUID
        | K_DATE
        | K_TIME
        | K_EMPTY
        ) { $str = $k.text; }
    ;

// Case-insensitive keywords
K_SELECT:      S E L E C T;
K_FROM:        F R O M;
K_AS:          A S;
K_CAST:        C A S T;
K_WHERE:       W H E R E;
K_AND:         A N D;
K_KEY:         K E Y;
K_KEYS:        K E Y S;
K_ENTRIES:     E N T R I E S;
K_FULL:        F U L L;
K_INSERT:      I N S E R T;
K_UPDATE:      U P D A T E;
K_WITH:        W I T H;
K_LIMIT:       L I M I T;
K_USING:       U S I N G;
K_USE:         U S E;
K_DISTINCT:    D I S T I N C T;
K_COUNT:       C O U N T;
K_SET:         S E T;
K_BEGIN:       B E G I N;
K_UNLOGGED:    U N L O G G E D;
K_BATCH:       B A T C H;
K_APPLY:       A P P L Y;
K_TRUNCATE:    T R U N C A T E;
K_DELETE:      D E L E T E;
K_IN:          I N;
K_CREATE:      C R E A T E;
K_SCHEMA:      S C H E M A;
K_KEYSPACE:    ( K E Y S P A C E
                 | K_SCHEMA );
K_KEYSPACES:   K E Y S P A C E S;
K_COLUMNFAMILY:( C O L U M N F A M I L Y
                 | T A B L E );
K_TABLES:      ( C O L U M N F A M I L I E S
                 | T A B L E S );
K_MATERIALIZED:M A T E R I A L I Z E D;
K_VIEW:        V I E W;
K_INDEX:       I N D E X;
K_CUSTOM:      C U S T O M;
K_ON:          O N;
K_TO:          T O;
K_DROP:        D R O P;
K_PRIMARY:     P R I M A R Y;
K_INTO:        I N T O;
K_VALUES:      V A L U E S;
K_TIMESTAMP:   T I M E S T A M P;
K_TTL:         T T L;
K_ALTER:       A L T E R;
K_RENAME:      R E N A M E;
K_ADD:         A D D;
K_TYPE:        T Y P E;
K_TYPES:       T Y P E S;
K_COMPACT:     C O M P A C T;
K_STORAGE:     S T O R A G E;
K_ORDER:       O R D E R;
K_BY:          B Y;
K_ASC:         A S C;
K_DESC:        D E S C;
K_ALLOW:       A L L O W;
K_FILTERING:   F I L T E R I N G;
K_IF:          I F;
K_IS:          I S;
K_CONTAINS:    C O N T A I N S;
K_INTERNALS:   I N T E R N A L S;
K_ONLY:        O N L Y;

K_GRANT:       G R A N T;
K_ALL:         A L L;
K_PERMISSION:  P E R M I S S I O N;
K_PERMISSIONS: P E R M I S S I O N S;
K_OF:          O F;
K_REVOKE:      R E V O K E;
K_MODIFY:      M O D I F Y;
K_AUTHORIZE:   A U T H O R I Z E;
K_DESCRIBE:    D E S C R I B E;
K_NORECURSIVE: N O R E C U R S I V E;

K_USER:        U S E R;
K_USERS:       U S E R S;
K_ROLE:        R O L E;
K_ROLES:       R O L E S;
K_SUPERUSER:   S U P E R U S E R;
K_NOSUPERUSER: N O S U P E R U S E R;
K_PASSWORD:    P A S S W O R D;
K_PASSWORDS:   P A S S W O R D S;
K_HASHED:      H A S H E D;
K_LOGIN:       L O G I N;
K_NOLOGIN:     N O L O G I N;
K_OPTIONS:     O P T I O N S;

K_CLUSTER:     C L U S T E R;
K_CLUSTERING:  C L U S T E R I N G;
K_ASCII:       A S C I I;
K_BIGINT:      B I G I N T;
K_BLOB:        B L O B;
K_BOOLEAN:     B O O L E A N;
K_COUNTER:     C O U N T E R;
K_DECIMAL:     D E C I M A L;
K_DOUBLE:      D O U B L E;
K_DURATION:    D U R A T I O N;
K_FLOAT:       F L O A T;
K_INET:        I N E T;
K_INT:         I N T;
K_SMALLINT:    S M A L L I N T;
K_TINYINT:     T I N Y I N T;
K_TEXT:        T E X T;
K_UUID:        U U I D;
K_VARCHAR:     V A R C H A R;
K_VARINT:      V A R I N T;
K_TIMEUUID:    T I M E U U I D;
K_TOKEN:       T O K E N;
K_WRITETIME:   W R I T E T I M E;
K_DATE:        D A T E;
K_TIME:        T I M E;

K_NULL:        N U L L;
K_NOT:         N O T;
K_EXISTS:      E X I S T S;

K_MAP:         M A P;
K_LIST:        L I S T;
K_NAN:         N A N;
K_INFINITY:    I N F I N I T Y;
K_TUPLE:       T U P L E;
K_VECTOR:      V E C T O R;

K_TRIGGER:     T R I G G E R;
K_STATIC:      S T A T I C;
K_FROZEN:      F R O Z E N;

K_FUNCTION:    F U N C T I O N;
K_FUNCTIONS:   F U N C T I O N S;
K_AGGREGATE:   A G G R E G A T E;
K_AGGREGATES:  A G G R E G A T E S;
K_SFUNC:       S F U N C;
K_STYPE:       S T Y P E;
K_REDUCEFUNC:  R E D U C E F U N C;
K_FINALFUNC:   F I N A L F U N C;
K_INITCOND:    I N I T C O N D;
K_RETURNS:     R E T U R N S;
K_CALLED:      C A L L E D;
K_INPUT:       I N P U T;
K_LANGUAGE:    L A N G U A G E;
K_OR:          O R;
K_REPLACE:     R E P L A C E;
K_JSON:        J S O N;
K_DEFAULT:     D E F A U L T;
K_UNSET:       U N S E T;

K_EMPTY:       E M P T Y;

K_BYPASS:      B Y P A S S;
K_CACHE:       C A C H E;

K_PER:         P E R;
K_PARTITION:   P A R T I T I O N;

K_SERVICE_LEVEL: S E R V I C E '_' L E V E L;
K_ATTACH: A T T A C H;
K_DETACH: D E T A C H;
K_SERVICE_LEVELS: S E R V I C E '_' L E V E L S;
K_ATTACHED: A T T A C H E D;
K_FOR: F O R;
K_SERVICE: S E R V I C E;
K_LEVEL: L E V E L;
K_LEVELS: L E V E L S;
K_EFFECTIVE: E F F E C T I V E;
K_SHARES: S H A R E S;

K_SCYLLA_TIMEUUID_LIST_INDEX: S C Y L L A '_' T I M E U U I D '_' L I S T '_' I N D E X;
K_SCYLLA_COUNTER_SHARD_LIST: S C Y L L A '_' C O U N T E R '_' S H A R D '_' L I S T; 
K_SCYLLA_CLUSTERING_BOUND: S C Y L L A '_' C L U S T E R I N G '_' B O U N D;


K_GROUP:       G R O U P;

K_LIKE:        L I K E;

K_TIMEOUT:     T I M E O U T;
K_PRUNE:       P R U N E;

K_EXECUTE:     E X E C U T E;

K_MUTATION_FRAGMENTS:    M U T A T I O N '_' F R A G M E N T S;

// Case-insensitive alpha characters
fragment A: ('a'|'A');
fragment B: ('b'|'B');
fragment C: ('c'|'C');
fragment D: ('d'|'D');
fragment E: ('e'|'E');
fragment F: ('f'|'F');
fragment G: ('g'|'G');
fragment H: ('h'|'H');
fragment I: ('i'|'I');
fragment J: ('j'|'J');
fragment K: ('k'|'K');
fragment L: ('l'|'L');
fragment M: ('m'|'M');
fragment N: ('n'|'N');
fragment O: ('o'|'O');
fragment P: ('p'|'P');
fragment Q: ('q'|'Q');
fragment R: ('r'|'R');
fragment S: ('s'|'S');
fragment T: ('t'|'T');
fragment U: ('u'|'U');
fragment V: ('v'|'V');
fragment W: ('w'|'W');
fragment X: ('x'|'X');
fragment Y: ('y'|'Y');
fragment Z: ('z'|'Z');

STRING_LITERAL
    @init{
        std::string txt; // temporary to build pg-style-string
    }
    @after{ 
        // This is an ugly hack that allows returning empty string literals.
        // If setText() was called with an empty string antlr3 would decide
        // that setText() was never called and just return the unmodified
        // token value. To prevent that we call setText() with non-empty string
        // that is not valid utf8 which will be later changed to an empty
        // string once it leaves antlr3 code.
        if (txt.empty()) {
            txt.push_back(-1);
        }
        setText(txt);
    }
    :
      /* pg-style string literal */
      (
        '$' '$'
        (
          (c=~('$') { txt.push_back(c); })
          |
          ('$' (c=~('$') { txt.push_back('$'); txt.push_back(c); }))
        )*
        '$' '$'
      )
      |
      /* conventional quoted string literal */
      (
        '\'' (c=~('\'') { txt.push_back(c);} | '\'' '\'' { txt.push_back('\''); })* '\''
      )
    ;

QUOTED_NAME
    @init{ std::string b; }
    @after{ setText(b); }
    : '\"' (c=~('\"') { b.push_back(c); } | '\"' '\"' { b.push_back('\"'); })+ '\"'
    ;

fragment DIGIT
    : '0'..'9'
    ;

fragment LETTER
    : ('A'..'Z' | 'a'..'z')
    ;

fragment HEX
    : ('A'..'F' | 'a'..'f' | '0'..'9')
    ;

fragment EXPONENT
    : E ('+' | '-')? DIGIT+
    ;

fragment DURATION_UNIT
    : Y
    | M O
    | W
    | D
    | H
    | M
    | S
    | M S
    | U S
    | '\u00B5' S
    | N S
    ;

INTEGER
    : '-'? DIGIT+
    ;

QMARK
    : '?'
    ;

/*
 * Normally a lexer only emits one token at a time, but ours is tricked out
 * to support multiple (see @lexer::members near the top of the grammar).
 */
FLOAT
    : INTEGER EXPONENT
    | INTEGER '.' DIGIT* EXPONENT?
    ;

/*
 * This has to be before IDENT so it takes precedence over it.
 */
BOOLEAN
    : T R U E | F A L S E
    ;

DURATION
    : '-'? DIGIT+ DURATION_UNIT (DIGIT+ DURATION_UNIT)*
    | '-'? 'P' (DIGIT+ 'Y')? (DIGIT+ 'M')? (DIGIT+ 'D')? ('T' (DIGIT+ 'H')? (DIGIT+ 'M')? (DIGIT+ 'S')?)? // ISO 8601 "format with designators"
    | '-'? 'P' DIGIT+ 'W'
    | '-'? 'P' DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT 'T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT // ISO 8601 "alternative format"
    ;

IDENT
    : LETTER (LETTER | DIGIT | '_')*
    ;

HEXNUMBER
    : '0' X HEX*
    ;

UUID
    : HEX HEX HEX HEX HEX HEX HEX HEX '-'
      HEX HEX HEX HEX '-'
      HEX HEX HEX HEX '-'
      HEX HEX HEX HEX '-'
      HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX
    ;

WS
    : (' ' | '\t' | '\n' | '\r')+ { $channel = HIDDEN; }
    ;

COMMENT
    : ('--' | '//') .* ('\n'|'\r') { $channel = HIDDEN; }
    ;

MULTILINE_COMMENT
    : '/*' .* '*/' { $channel = HIDDEN; }
    ;
