#include "sql_expression.h"
#include "sql_call_expr.h"
#include "sql_select.h"
#include "sql_values.h"
#include <yql/essentials/parser/proto_ast/gen/v1/SQLv1Lexer.h>
#include <yql/essentials/parser/proto_ast/gen/v1_antlr4/SQLv1Antlr4Lexer.h>
#include <yql/essentials/utils/utf8.h>
#include <util/charset/wide.h>
#include <util/string/ascii.h>
#include <util/string/hex.h>

namespace NSQLTranslationV1 {

using NALPDefault::SQLv1LexerTokens;
using NALPDefaultAntlr4::SQLv1Antlr4Lexer;

using namespace NSQLv1Generated;

TNodePtr TSqlExpression::Build(const TRule_expr& node) {
        // expr:
        //     or_subexpr (OR or_subexpr)*
        //   | type_name_composite
        switch (node.Alt_case()) {
            case TRule_expr::kAltExpr1: {
                auto getNode = [](const TRule_expr_TAlt1_TBlock2& b) -> const TRule_or_subexpr& { return b.GetRule_or_subexpr2(); };
                return BinOper("Or", node.GetAlt_expr1().GetRule_or_subexpr1(), getNode,
                    node.GetAlt_expr1().GetBlock2().begin(), node.GetAlt_expr1().GetBlock2().end(), {});
            }
            case TRule_expr::kAltExpr2: {
                return TypeNode(node.GetAlt_expr2().GetRule_type_name_composite1());
            }
            case TRule_expr::ALT_NOT_SET:
                Y_ABORT("You should change implementation according to grammar changes");
        }
    }

TNodePtr TSqlExpression::Build(const TRule_lambda_or_parameter& node) {
        // lambda_or_parameter:
        //    lambda
        //  | bind_parameter
        switch (node.Alt_case()) {
            case TRule_lambda_or_parameter::kAltLambdaOrParameter1: {
                return LambdaRule(node.alt_lambda_or_parameter1().GetRule_lambda1());
            }
            case TRule_lambda_or_parameter::kAltLambdaOrParameter2: {
                TString named;
                if (!NamedNodeImpl(node.GetAlt_lambda_or_parameter2().GetRule_bind_parameter1(), named, *this)) {
                    return nullptr;
                }
                auto namedNode = GetNamedNode(named);
                if (!namedNode) {
                    return nullptr;
                }

                return namedNode;
            }
            case TRule_lambda_or_parameter::ALT_NOT_SET:
                Y_ABORT("You should change implementation according to grammar changes");
        }
    }

TNodePtr TSqlExpression::SubExpr(const TRule_mul_subexpr& node, const TTrailingQuestions& tail) {
        // mul_subexpr: con_subexpr (DOUBLE_PIPE con_subexpr)*;
        auto getNode = [](const TRule_mul_subexpr::TBlock2& b) -> const TRule_con_subexpr& { return b.GetRule_con_subexpr2(); };
        return BinOper("Concat", node.GetRule_con_subexpr1(), getNode, node.GetBlock2().begin(), node.GetBlock2().end(), tail);
}

TNodePtr TSqlExpression::SubExpr(const TRule_add_subexpr& node, const TTrailingQuestions& tail) {
        // add_subexpr: mul_subexpr ((ASTERISK | SLASH | PERCENT) mul_subexpr)*;
        auto getNode = [](const TRule_add_subexpr::TBlock2& b) -> const TRule_mul_subexpr& { return b.GetRule_mul_subexpr2(); };
        return BinOpList(node.GetRule_mul_subexpr1(), getNode, node.GetBlock2().begin(), node.GetBlock2().end(), tail);
}

TNodePtr TSqlExpression::SubExpr(const TRule_bit_subexpr& node, const TTrailingQuestions& tail) {
        // bit_subexpr: add_subexpr ((PLUS | MINUS) add_subexpr)*;
        auto getNode = [](const TRule_bit_subexpr::TBlock2& b) -> const TRule_add_subexpr& { return b.GetRule_add_subexpr2(); };
        return BinOpList(node.GetRule_add_subexpr1(), getNode, node.GetBlock2().begin(), node.GetBlock2().end(), tail);
    }

TNodePtr TSqlExpression::SubExpr(const TRule_neq_subexpr& node, const TTrailingQuestions& tailExternal) {
        //neq_subexpr: bit_subexpr ((SHIFT_LEFT | shift_right | ROT_LEFT | rot_right | AMPERSAND | PIPE | CARET) bit_subexpr)*
        //  // trailing QUESTIONS are used in optional simple types (String?) and optional lambda args: ($x, $y?) -> ($x)
        //  ((double_question neq_subexpr) => double_question neq_subexpr | QUESTION+)?;
        YQL_ENSURE(tailExternal.Count == 0);
        MaybeUnnamedSmartParenOnTop = MaybeUnnamedSmartParenOnTop && !node.HasBlock3();
        TTrailingQuestions tail;
        if (node.HasBlock3() && node.GetBlock3().Alt_case() == TRule_neq_subexpr::TBlock3::kAlt2) {
            auto& questions = node.GetBlock3().GetAlt2();
            tail.Count = questions.GetBlock1().size();
            tail.Pos = Ctx.TokenPosition(questions.GetBlock1().begin()->GetToken1());
            YQL_ENSURE(tail.Count > 0);
        }

        auto getNode = [](const TRule_neq_subexpr::TBlock2& b) -> const TRule_bit_subexpr& { return b.GetRule_bit_subexpr2(); };
        auto result = BinOpList(node.GetRule_bit_subexpr1(), getNode, node.GetBlock2().begin(), node.GetBlock2().end(), tail);
        if (!result) {
            return {};
        }
        if (node.HasBlock3()) {
            auto& block = node.GetBlock3();
            if (block.Alt_case() == TRule_neq_subexpr::TBlock3::kAlt1) {
                TSqlExpression altExpr(Ctx, Mode);
                auto altResult = SubExpr(block.GetAlt1().GetRule_neq_subexpr2(), {});
                if (!altResult) {
                    return {};
                }
                const TVector<TNodePtr> args({result, altResult});
                Token(block.GetAlt1().GetRule_double_question1().GetToken1());
                result = BuildBuiltinFunc(Ctx, Ctx.Pos(), "Coalesce", args);
            }
        }
        return result;
    }

    TNodePtr TSqlExpression::SubExpr(const TRule_eq_subexpr& node, const TTrailingQuestions& tail) {
        // eq_subexpr: neq_subexpr ((LESS | LESS_OR_EQ | GREATER | GREATER_OR_EQ) neq_subexpr)*;
        auto getNode = [](const TRule_eq_subexpr::TBlock2& b) -> const TRule_neq_subexpr& { return b.GetRule_neq_subexpr2(); };
        return BinOpList(node.GetRule_neq_subexpr1(), getNode, node.GetBlock2().begin(), node.GetBlock2().end(), tail);
    }

    TNodePtr TSqlExpression::SubExpr(const TRule_or_subexpr& node, const TTrailingQuestions& tail) {
        // or_subexpr: and_subexpr (AND and_subexpr)*;
        auto getNode = [](const TRule_or_subexpr::TBlock2& b) -> const TRule_and_subexpr& { return b.GetRule_and_subexpr2(); };
        return BinOper("And", node.GetRule_and_subexpr1(), getNode, node.GetBlock2().begin(), node.GetBlock2().end(), tail);
}

TNodePtr TSqlExpression::SubExpr(const TRule_and_subexpr& node, const TTrailingQuestions& tail) {
        // and_subexpr: xor_subexpr (XOR xor_subexpr)*;
        auto getNode = [](const TRule_and_subexpr::TBlock2& b) -> const TRule_xor_subexpr& { return b.GetRule_xor_subexpr2(); };
        return BinOper("Xor", node.GetRule_xor_subexpr1(), getNode, node.GetBlock2().begin(), node.GetBlock2().end(), tail);
}


bool ChangefeedSettingsEntry(const TRule_changefeed_settings_entry& node, TSqlExpression& ctx, TChangefeedSettings& settings, bool alter) {
    const auto id = IdEx(node.GetRule_an_id1(), ctx);
    if (alter) {
        // currently we don't support alter settings
        ctx.Error() << to_upper(id.Name) << " alter is not supported";
        return false;
    }

    const auto& setting = node.GetRule_changefeed_setting_value3();
    auto exprNode = ctx.Build(setting.GetRule_expr1());

    if (!exprNode) {
        ctx.Context().Error(id.Pos) << "Invalid changefeed setting: " << id.Name;
        return false;
    }

    if (to_lower(id.Name) == "sink_type") {
        if (!exprNode->IsLiteral() || exprNode->GetLiteralType() != "String") {
            ctx.Context().Error() << "Literal of String type is expected for " << id.Name;
            return false;
        }

        const auto value = exprNode->GetLiteralValue();
        if (to_lower(value) == "local") {
            settings.SinkSettings = TChangefeedSettings::TLocalSinkSettings();
        } else {
            ctx.Context().Error() << "Unknown changefeed sink type: " << value;
            return false;
        }
    } else if (to_lower(id.Name) == "mode") {
        if (!exprNode->IsLiteral() || exprNode->GetLiteralType() != "String") {
            ctx.Context().Error() << "Literal of String type is expected for " << id.Name;
            return false;
        }
        settings.Mode = exprNode;
    } else if (to_lower(id.Name) == "format") {
        if (!exprNode->IsLiteral() || exprNode->GetLiteralType() != "String") {
            ctx.Context().Error() << "Literal of String type is expected for " << id.Name;
            return false;
        }
        settings.Format = exprNode;
    } else if (to_lower(id.Name) == "initial_scan") {
        if (!exprNode->IsLiteral() || exprNode->GetLiteralType() != "Bool") {
            ctx.Context().Error() << "Literal of Bool type is expected for " << id.Name;
            return false;
        }
        settings.InitialScan = exprNode;
    } else if (to_lower(id.Name) == "virtual_timestamps") {
        if (!exprNode->IsLiteral() || exprNode->GetLiteralType() != "Bool") {
            ctx.Context().Error() << "Literal of Bool type is expected for " << id.Name;
            return false;
        }
        settings.VirtualTimestamps = exprNode;
    } else if (to_lower(id.Name) == "barriers_interval" || to_lower(id.Name) == "resolved_timestamps") {
        if (exprNode->GetOpName() != "Interval") {
            ctx.Context().Error() << "Literal of Interval type is expected for " << id.Name;
            return false;
        }
        settings.BarriersInterval = exprNode;
    } else if (to_lower(id.Name) == "retention_period") {
        if (exprNode->GetOpName() != "Interval") {
            ctx.Context().Error() << "Literal of Interval type is expected for " << id.Name;
            return false;
        }
        settings.RetentionPeriod = exprNode;
    } else if (to_lower(id.Name) == "topic_auto_partitioning") {
        auto v = to_lower(exprNode->GetLiteralValue());
        if (v != "enabled" && v != "disabled") {
            ctx.Context().Error() << "Literal of Interval type is expected for " << id.Name;
        }
        settings.TopicAutoPartitioning = exprNode;
    } else if (to_lower(id.Name) == "topic_max_active_partitions") {
        if (!exprNode->IsIntegerLiteral()) {
            ctx.Context().Error() << "Literal of integer type is expected for " << id.Name;
            return false;
        }
        settings.TopicMaxActivePartitions = exprNode;
    } else if (to_lower(id.Name) == "topic_min_active_partitions") {
        if (!exprNode->IsIntegerLiteral()) {
            ctx.Context().Error() << "Literal of integer type is expected for " << id.Name;
            return false;
        }
        settings.TopicPartitions = exprNode;
    } else if (to_lower(id.Name) == "aws_region") {
        if (!exprNode->IsLiteral() || exprNode->GetLiteralType() != "String") {
            ctx.Context().Error() << "Literal of String type is expected for " << id.Name;
            return false;
        }
        settings.AwsRegion = exprNode;
    } else {
        ctx.Context().Error(id.Pos) << "Unknown changefeed setting: " << id.Name;
        return false;
    }

    return true;
}

bool ChangefeedSettings(const TRule_changefeed_settings& node, TSqlExpression& ctx, TChangefeedSettings& settings, bool alter) {
    if (!ChangefeedSettingsEntry(node.GetRule_changefeed_settings_entry1(), ctx, settings, alter)) {
        return false;
    }

    for (auto& block : node.GetBlock2()) {
        if (!ChangefeedSettingsEntry(block.GetRule_changefeed_settings_entry2(), ctx, settings, alter)) {
            return false;
        }
    }

    return true;
}

bool CreateChangefeed(const TRule_changefeed& node, TSqlExpression& ctx, TVector<TChangefeedDescription>& changefeeds) {
    changefeeds.emplace_back(IdEx(node.GetRule_an_id2(), ctx));

    if (!ChangefeedSettings(node.GetRule_changefeed_settings5(), ctx, changefeeds.back().Settings, false)) {
        return false;
    }

    return true;
}

namespace {
    bool WithoutAlpha(const std::string_view &literal) {
        return literal.cend() == std::find_if(literal.cbegin(), literal.cend(), [](char c) { return std::isalpha(c) || (c & '\x80'); });
    }
}


bool Expr(TSqlExpression& sqlExpr, TVector<TNodePtr>& exprNodes, const TRule_expr& node) {
    TNodePtr exprNode = sqlExpr.Build(node);
    if (!exprNode) {
        return false;
    }
    exprNodes.push_back(exprNode);
    return true;
}

bool ExprList(TSqlExpression& sqlExpr, TVector<TNodePtr>& exprNodes, const TRule_expr_list& node) {
    if (!Expr(sqlExpr, exprNodes, node.GetRule_expr1())) {
        return false;
    }
    for (auto b: node.GetBlock2()) {
        sqlExpr.Token(b.GetToken1());
        if (!Expr(sqlExpr, exprNodes, b.GetRule_expr2())) {
            return false;
        }
    }
    return true;
}
bool ParseNumbers(TContext& ctx, const TString& strOrig, ui64& value, TString& suffix) {
    const auto str = to_lower(strOrig);
    const auto strLen = str.size();
    ui64 base = 10;
    if (strLen > 2 && str[0] == '0') {
        const auto formatChar = str[1];
        if (formatChar == 'x') {
            base = 16;
        } else if (formatChar == 'o') {
            base = 8;
        } else if (formatChar == 'b') {
            base = 2;
        }
    }
    if (strLen > 1) {
        auto iter = str.cend() - 1;
        if (*iter == 'l' || *iter == 's' || *iter == 't' || *iter == 's' || *iter == 'i' ||  *iter == 'b' || *iter == 'n') {
            --iter;
        }
        if (*iter == 'u' || *iter == 'p') {
            --iter;
        }
        suffix = TString(++iter, str.cend());
    }
    value = 0;
    const TString digString(str.begin() + (base == 10 ? 0 : 2), str.end() - suffix.size());
    for (const char& cur: digString) {
        const ui64 curDigit = Char2DigitTable[static_cast<int>(cur)];
        if (curDigit >= base) {
            ctx.Error(ctx.Pos()) << "Failed to parse number from string: " << strOrig << ", char: '" << cur <<
                "' is out of base: " << base;
            return false;
        }

        ui64 curValue = value;
        value *= base;
        bool overflow = ((value / base) != curValue);
        if (!overflow) {
            curValue = value;
            value += curDigit;
            overflow = value < curValue;
        }

        if (overflow) {
            ctx.Error(ctx.Pos()) << "Failed to parse number from string: " << strOrig << ", number limit overflow";
            return false;
        }
    }
    return true;
}

TNodePtr LiteralNumber(TContext& ctx, const TRule_integer& node) {
    const TString intergerString = ctx.Token(node.GetToken1());
    if (to_lower(intergerString).EndsWith("pn")) {
        // TODO: add validation
        return new TLiteralNode(ctx.Pos(), "PgNumeric", intergerString.substr(0, intergerString.size() - 2));
    }

    ui64 value;
    TString suffix;
    if (!ParseNumbers(ctx, intergerString, value, suffix)) {
        return {};
    }

    const bool noSpaceForInt32 = value >> 31;
    const bool noSpaceForInt64 = value >> 63;
    if (suffix ==  "") {
        bool implicitType = true;
        if (noSpaceForInt64) {
            return new TLiteralNumberNode<ui64>(ctx.Pos(), "Uint64", ToString(value), implicitType);
        } else if (noSpaceForInt32) {
            return new TLiteralNumberNode<i64>(ctx.Pos(), "Int64", ToString(value), implicitType);
        }
        return new TLiteralNumberNode<i32>(ctx.Pos(), "Int32", ToString(value), implicitType);
    } else if (suffix == "p") {
        bool implicitType = true;
        if (noSpaceForInt64) {
            ctx.Error(ctx.Pos()) << "Failed to parse number from string: " << intergerString << ", 64 bit signed integer overflow";
            return {};
        } else if (noSpaceForInt32) {
            return new TLiteralNumberNode<i64>(ctx.Pos(), "PgInt8", ToString(value), implicitType);
        }
        return new TLiteralNumberNode<i32>(ctx.Pos(), "PgInt4", ToString(value), implicitType);
    } else if (suffix == "u") {
        return new TLiteralNumberNode<ui32>(ctx.Pos(), "Uint32", ToString(value));
    } else if (suffix == "ul") {
        return new TLiteralNumberNode<ui64>(ctx.Pos(), "Uint64", ToString(value));
    } else if (suffix == "ut") {
        return new TLiteralNumberNode<ui8>(ctx.Pos(), "Uint8", ToString(value));
    } else if (suffix == "t") {
        return new TLiteralNumberNode<i8>(ctx.Pos(), "Int8", ToString(value));
    } else if (suffix == "l") {
        return new TLiteralNumberNode<i64>(ctx.Pos(), "Int64", ToString(value));
    } else if (suffix == "us") {
        return new TLiteralNumberNode<ui16>(ctx.Pos(), "Uint16", ToString(value));
    } else if (suffix == "s") {
        return new TLiteralNumberNode<i16>(ctx.Pos(), "Int16", ToString(value));
    } else if (suffix == "ps") {
        return new TLiteralNumberNode<i16>(ctx.Pos(), "PgInt2", ToString(value));
    } else if (suffix == "pi") {
        return new TLiteralNumberNode<i32>(ctx.Pos(), "PgInt4", ToString(value));
    } else if (suffix == "pb") {
        return new TLiteralNumberNode<i64>(ctx.Pos(), "PgInt8", ToString(value));
    } else {
        ctx.Error(ctx.Pos()) << "Failed to parse number from string: " << intergerString << ", invalid suffix: " << suffix;
        return {};
    }
}

TNodePtr LiteralReal(TContext& ctx, const TRule_real& node) {
    const TString value(ctx.Token(node.GetToken1()));
    YQL_ENSURE(!value.empty());
    auto lower = to_lower(value);
    if (lower.EndsWith("f")) {
        return new TLiteralNumberNode<float>(ctx.Pos(), "Float", value.substr(0, value.size()-1));
    } else if (lower.EndsWith("p")) {
        return new TLiteralNumberNode<float>(ctx.Pos(), "PgFloat8", value.substr(0, value.size()-1));
    } else if (lower.EndsWith("pf4")) {
        return new TLiteralNumberNode<float>(ctx.Pos(), "PgFloat4", value.substr(0, value.size()-3));
    } else if (lower.EndsWith("pf8")) {
        return new TLiteralNumberNode<float>(ctx.Pos(), "PgFloat8", value.substr(0, value.size()-3));
    } else if (lower.EndsWith("pn")) {
        return new TLiteralNode(ctx.Pos(), "PgNumeric", value.substr(0, value.size()-2));
    } else {
        return new TLiteralNumberNode<double>(ctx.Pos(), "Double", value);
    }
}

TMaybe<TExprOrIdent> TSqlExpression::LiteralExpr(const TRule_literal_value& node) {
    TExprOrIdent result;
    switch (node.Alt_case()) {
        case TRule_literal_value::kAltLiteralValue1: {
            result.Expr = LiteralNumber(Ctx, node.GetAlt_literal_value1().GetRule_integer1());
            break;
        }
        case TRule_literal_value::kAltLiteralValue2: {
            result.Expr = LiteralReal(Ctx, node.GetAlt_literal_value2().GetRule_real1());
            break;
        }
        case TRule_literal_value::kAltLiteralValue3: {
            const TString value(Token(node.GetAlt_literal_value3().GetToken1()));
            return BuildLiteralTypedSmartStringOrId(Ctx, value);
        }
        case TRule_literal_value::kAltLiteralValue5: {
            Token(node.GetAlt_literal_value5().GetToken1());
            result.Expr = BuildLiteralNull(Ctx.Pos());
            break;
        }
        case TRule_literal_value::kAltLiteralValue9: {
            const TString value(to_lower(Token(node.GetAlt_literal_value9().GetRule_bool_value1().GetToken1())));
            result.Expr = BuildLiteralBool(Ctx.Pos(), FromString<bool>(value));
            break;
        }
        case TRule_literal_value::kAltLiteralValue10: {
            result.Expr = BuildEmptyAction(Ctx.Pos());
            break;
        }
        case TRule_literal_value::kAltLiteralValue4:
        case TRule_literal_value::kAltLiteralValue6:
        case TRule_literal_value::kAltLiteralValue7:
        case TRule_literal_value::kAltLiteralValue8:
        case TRule_literal_value::ALT_NOT_SET:
            AltNotImplemented("literal_value", node);
    }
    if (!result.Expr) {
        return {};
    }
    return result;
}

template<typename TUnarySubExprType>
TNodePtr TSqlExpression::UnaryExpr(const TUnarySubExprType& node, const TTrailingQuestions& tail) {
    if constexpr (std::is_same_v<TUnarySubExprType, TRule_unary_subexpr>) {
        if (node.Alt_case() == TRule_unary_subexpr::kAltUnarySubexpr1) {
            return UnaryCasualExpr(node.GetAlt_unary_subexpr1().GetRule_unary_casual_subexpr1(), tail);
        } else if (tail.Count) {
            UnexpectedQuestionToken(tail);
            return {};
        } else {
            MaybeUnnamedSmartParenOnTop = false;
            return JsonApiExpr(node.GetAlt_unary_subexpr2().GetRule_json_api_expr1());
        }
    } else {
        MaybeUnnamedSmartParenOnTop = false;
        if (node.Alt_case() == TRule_in_unary_subexpr::kAltInUnarySubexpr1) {
            return UnaryCasualExpr(node.GetAlt_in_unary_subexpr1().GetRule_in_unary_casual_subexpr1(), tail);
        } else if (tail.Count) {
            UnexpectedQuestionToken(tail);
            return {};
        } else {
            return JsonApiExpr(node.GetAlt_in_unary_subexpr2().GetRule_json_api_expr1());
        }
    }
}

TNodePtr TSqlExpression::JsonPathSpecification(const TRule_jsonpath_spec& node) {
    /*
        jsonpath_spec: STRING_VALUE;
    */
    TString value = Token(node.GetToken1());
    TPosition pos = Ctx.Pos();

    auto parsed = StringContent(Ctx, pos, value);
    if (!parsed) {
        return nullptr;
    }
    return new TCallNodeImpl(pos, "Utf8", {BuildQuotedAtom(pos, parsed->Content, parsed->Flags)});
}

TNodePtr TSqlExpression::JsonReturningTypeRule(const TRule_type_name_simple& node) {
    /*
        (RETURNING type_name_simple)?
    */
    return TypeSimple(node, /* onlyDataAllowed */ true);
}

TNodePtr TSqlExpression::JsonInputArg(const TRule_json_common_args& node) {
    /*
        json_common_args: expr COMMA jsonpath_spec (PASSING json_variables)?;
    */
    TNodePtr jsonExpr = Build(node.GetRule_expr1());
    if (!jsonExpr || jsonExpr->IsNull()) {
        jsonExpr = new TCallNodeImpl(Ctx.Pos(), "Nothing", {
            new TCallNodeImpl(Ctx.Pos(), "OptionalType", {BuildDataType(Ctx.Pos(), "Json")})
        });
    }

    return jsonExpr;
}

void TSqlExpression::AddJsonVariable(const TRule_json_variable& node, TVector<TNodePtr>& children) {
    /*
        json_variable: expr AS json_variable_name;
    */
    TNodePtr expr;
    TString rawName;
    TPosition namePos = Ctx.Pos();
    ui32 nameFlags = 0;

    expr = Build(node.GetRule_expr1());
    const auto& nameRule = node.GetRule_json_variable_name3();
    switch (nameRule.GetAltCase()) {
        case TRule_json_variable_name::kAltJsonVariableName1:
            rawName = Id(nameRule.GetAlt_json_variable_name1().GetRule_id_expr1(), *this);
            nameFlags = TNodeFlags::ArbitraryContent;
            break;
        case TRule_json_variable_name::kAltJsonVariableName2: {
            const auto& token = nameRule.GetAlt_json_variable_name2().GetToken1();
            namePos = GetPos(token);
            auto parsed = StringContentOrIdContent(Ctx, namePos, token.GetValue());
            if (!parsed) {
                return;
            }
            rawName = parsed->Content;
            nameFlags = parsed->Flags;
            break;
        }
        case TRule_json_variable_name::ALT_NOT_SET:
            Y_ABORT("You should change implementation according to grammar changes");
    }

    TNodePtr nameExpr = BuildQuotedAtom(namePos, rawName, nameFlags);
    children.push_back(BuildTuple(namePos, {nameExpr, expr}));
}

void TSqlExpression::AddJsonVariables(const TRule_json_variables& node, TVector<TNodePtr>& children) {
    /*
        json_variables: json_variable (COMMA json_variable)*;
    */
    AddJsonVariable(node.GetRule_json_variable1(), children);
    for (size_t i = 0; i < node.Block2Size(); i++) {
            AddJsonVariable(node.GetBlock2(i).GetRule_json_variable2(), children);
    }
}

TNodePtr TSqlExpression::JsonVariables(const TRule_json_common_args& node) {
    /*
        json_common_args: expr COMMA jsonpath_spec (PASSING json_variables)?;
    */
    TVector<TNodePtr> variables;
    TPosition pos = Ctx.Pos();
    if (node.HasBlock4()) {
        const auto& block = node.GetBlock4();
        pos = GetPos(block.GetToken1());
        AddJsonVariables(block.GetRule_json_variables2(), variables);
    }
    return new TCallNodeImpl(pos, "JsonVariables", variables);
}

void TSqlExpression::AddJsonCommonArgs(const TRule_json_common_args& node, TVector<TNodePtr>& children) {
    /*
        json_common_args: expr COMMA jsonpath_spec (PASSING json_variables)?;
    */
    TNodePtr jsonExpr = JsonInputArg(node);
    TNodePtr jsonPath = JsonPathSpecification(node.GetRule_jsonpath_spec3());
    TNodePtr variables = JsonVariables(node);

    children.push_back(jsonExpr);
    children.push_back(jsonPath);
    children.push_back(variables);
}

TNodePtr TSqlExpression::JsonValueCaseHandler(const TRule_json_case_handler& node, EJsonValueHandlerMode& mode) {
    /*
        json_case_handler: ERROR | NULL | (DEFAULT expr);
    */

    switch (node.GetAltCase()) {
        case TRule_json_case_handler::kAltJsonCaseHandler1: {
            const auto pos = GetPos(node.GetAlt_json_case_handler1().GetToken1());
            mode = EJsonValueHandlerMode::Error;
            return new TCallNodeImpl(pos, "Null", {});
        }
        case TRule_json_case_handler::kAltJsonCaseHandler2: {
            const auto pos = GetPos(node.GetAlt_json_case_handler2().GetToken1());
            mode = EJsonValueHandlerMode::DefaultValue;
            return new TCallNodeImpl(pos, "Null", {});
        }
        case TRule_json_case_handler::kAltJsonCaseHandler3:
            mode = EJsonValueHandlerMode::DefaultValue;
            return Build(node.GetAlt_json_case_handler3().GetRule_expr2());
        case TRule_json_case_handler::ALT_NOT_SET:
            Y_ABORT("You should change implementation according to grammar changes");
    }
}

void TSqlExpression::AddJsonValueCaseHandlers(const TRule_json_value& node, TVector<TNodePtr>& children) {
    /*
        json_case_handler*
    */
    if (node.Block5Size() > 2) {
        Ctx.Error() << "Only 1 ON EMPTY and/or 1 ON ERROR clause is expected";
        Ctx.IncrementMonCounter("sql_errors", "JsonValueTooManyHandleClauses");
        return;
    }

    TNodePtr onEmpty;
    EJsonValueHandlerMode onEmptyMode = EJsonValueHandlerMode::DefaultValue;
    TNodePtr onError;
    EJsonValueHandlerMode onErrorMode = EJsonValueHandlerMode::DefaultValue;
    for (size_t i = 0; i < node.Block5Size(); i++) {
        const auto block = node.GetBlock5(i);
        const bool isEmptyClause = to_lower(block.GetToken3().GetValue()) == "empty";

        if (isEmptyClause && onEmpty != nullptr) {
            Ctx.Error() << "Only 1 ON EMPTY clause is expected";
            Ctx.IncrementMonCounter("sql_errors", "JsonValueMultipleOnEmptyClauses");
            return;
        }

        if (!isEmptyClause && onError != nullptr) {
            Ctx.Error() << "Only 1 ON ERROR clause is expected";
            Ctx.IncrementMonCounter("sql_errors", "JsonValueMultipleOnErrorClauses");
            return;
        }

        if (isEmptyClause && onError != nullptr) {
            Ctx.Error() << "ON EMPTY clause must be before ON ERROR clause";
            Ctx.IncrementMonCounter("sql_errors", "JsonValueOnEmptyAfterOnError");
            return;
        }

        EJsonValueHandlerMode currentMode;
        TNodePtr currentHandler = JsonValueCaseHandler(block.GetRule_json_case_handler1(), currentMode);

        if (isEmptyClause) {
            onEmpty = currentHandler;
            onEmptyMode = currentMode;
        } else {
            onError = currentHandler;
            onErrorMode = currentMode;
        }
    }

    if (onEmpty == nullptr) {
        onEmpty = new TCallNodeImpl(Ctx.Pos(), "Null", {});
    }

    if (onError == nullptr) {
        onError = new TCallNodeImpl(Ctx.Pos(), "Null", {});
    }

    children.push_back(BuildQuotedAtom(Ctx.Pos(), ToString(onEmptyMode), TNodeFlags::Default));
    children.push_back(onEmpty);
    children.push_back(BuildQuotedAtom(Ctx.Pos(), ToString(onErrorMode), TNodeFlags::Default));
    children.push_back(onError);
}

TNodePtr TSqlExpression::JsonValueExpr(const TRule_json_value& node) {
    /*
        json_value: JSON_VALUE LPAREN
            json_common_args
            (RETURNING type_name_simple)?
            (json_case_handler ON (EMPTY | ERROR))*
        RPAREN;
    */
    TVector<TNodePtr> children;
    AddJsonCommonArgs(node.GetRule_json_common_args3(), children);
    AddJsonValueCaseHandlers(node, children);

    if (node.HasBlock4()) {
        auto returningType = JsonReturningTypeRule(node.GetBlock4().GetRule_type_name_simple2());
        if (!returningType) {
            return {};
        }
        children.push_back(returningType);
    }

    return new TCallNodeImpl(GetPos(node.GetToken1()), "JsonValue", children);
}

void TSqlExpression::AddJsonExistsHandler(const TRule_json_exists& node, TVector<TNodePtr>& children) {
    /*
        json_exists: JSON_EXISTS LPAREN
            json_common_args
            json_exists_handler?
        RPAREN;
    */
    auto buildJustBool = [&](const TPosition& pos, bool value) {
        return new TCallNodeImpl(pos, "Just", {BuildLiteralBool(pos, value)});
    };

    if (!node.HasBlock4()) {
        children.push_back(buildJustBool(Ctx.Pos(), false));
        return;
    }

    const auto& handlerRule = node.GetBlock4().GetRule_json_exists_handler1();
    const auto& token = handlerRule.GetToken1();
    const auto pos = GetPos(token);
    const auto mode = to_lower(token.GetValue());
    if (mode == "unknown") {
        const auto nothingNode = new TCallNodeImpl(pos, "Nothing", {
            new TCallNodeImpl(pos, "OptionalType", {BuildDataType(pos, "Bool")})
        });
        children.push_back(nothingNode);
    } else if (mode != "error") {
        children.push_back(buildJustBool(pos, FromString<bool>(mode)));
    }
}

TNodePtr TSqlExpression::JsonExistsExpr(const TRule_json_exists& node) {
    /*
        json_exists: JSON_EXISTS LPAREN
            json_common_args
            json_exists_handler?
        RPAREN;
    */
    TVector<TNodePtr> children;
    AddJsonCommonArgs(node.GetRule_json_common_args3(), children);

    AddJsonExistsHandler(node, children);

    return new TCallNodeImpl(GetPos(node.GetToken1()), "JsonExists", children);
}

EJsonQueryWrap TSqlExpression::JsonQueryWrapper(const TRule_json_query& node) {
    /*
        json_query: JSON_QUERY LPAREN
            json_common_args
            (json_query_wrapper WRAPPER)?
            (json_query_handler ON EMPTY)?
            (json_query_handler ON ERROR)?
        RPAREN;
    */
    // default behaviour - no wrapping
    if (!node.HasBlock4()) {
        return EJsonQueryWrap::NoWrap;
    }

    // WITHOUT ARRAY? - no wrapping
    const auto& wrapperRule = node.GetBlock4().GetRule_json_query_wrapper1();
    if (wrapperRule.GetAltCase() == TRule_json_query_wrapper::kAltJsonQueryWrapper1) {
        return EJsonQueryWrap::NoWrap;
    }

    // WITH (CONDITIONAL | UNCONDITIONAL)? ARRAY? - wrapping depends on 2nd token. Default is UNCONDITIONAL
    const auto& withWrapperRule = wrapperRule.GetAlt_json_query_wrapper2();
    if (!withWrapperRule.HasBlock2()) {
        return EJsonQueryWrap::Wrap;
    }

    const auto& token = withWrapperRule.GetBlock2().GetToken1();
    if (to_lower(token.GetValue()) == "conditional") {
        return EJsonQueryWrap::ConditionalWrap;
    } else {
        return EJsonQueryWrap::Wrap;
    }
}

EJsonQueryHandler TSqlExpression::JsonQueryHandler(const TRule_json_query_handler& node) {
    /*
        json_query_handler: ERROR | NULL | (EMPTY ARRAY) | (EMPTY OBJECT);
    */
    switch (node.GetAltCase()) {
        case TRule_json_query_handler::kAltJsonQueryHandler1:
            return EJsonQueryHandler::Error;
        case TRule_json_query_handler::kAltJsonQueryHandler2:
            return EJsonQueryHandler::Null;
        case TRule_json_query_handler::kAltJsonQueryHandler3:
            return EJsonQueryHandler::EmptyArray;
        case TRule_json_query_handler::kAltJsonQueryHandler4:
            return EJsonQueryHandler::EmptyObject;
        case TRule_json_query_handler::ALT_NOT_SET:
            Y_ABORT("You should change implementation according to grammar changes");
    }
}

TNodePtr TSqlExpression::JsonQueryExpr(const TRule_json_query& node) {
    /*
        json_query: JSON_QUERY LPAREN
            json_common_args
            (json_query_wrapper WRAPPER)?
            (json_query_handler ON EMPTY)?
            (json_query_handler ON ERROR)?
        RPAREN;
    */

    TVector<TNodePtr> children;
    AddJsonCommonArgs(node.GetRule_json_common_args3(), children);

    auto addChild = [&](TPosition pos, const TString& content) {
        children.push_back(BuildQuotedAtom(pos, content, TNodeFlags::Default));
    };

    const auto wrapMode = JsonQueryWrapper(node);
    addChild(Ctx.Pos(), ToString(wrapMode));

    auto onEmpty = EJsonQueryHandler::Null;
    if (node.HasBlock5()) {
        if (wrapMode != EJsonQueryWrap::NoWrap) {
            Ctx.Error() << "ON EMPTY is prohibited because WRAPPER clause is specified";
            Ctx.IncrementMonCounter("sql_errors", "JsonQueryOnEmptyWithWrapper");
            return nullptr;
        }
        onEmpty = JsonQueryHandler(node.GetBlock5().GetRule_json_query_handler1());
    }
    addChild(Ctx.Pos(), ToString(onEmpty));

    auto onError = EJsonQueryHandler::Null;
    if (node.HasBlock6()) {
        onError = JsonQueryHandler(node.GetBlock6().GetRule_json_query_handler1());
    }
    addChild(Ctx.Pos(), ToString(onError));

    return new TCallNodeImpl(GetPos(node.GetToken1()), "JsonQuery", children);
}

TNodePtr TSqlExpression::JsonApiExpr(const TRule_json_api_expr& node) {
    /*
        json_api_expr: json_value | json_exists | json_query;
    */
    TPosition pos = Ctx.Pos();
    TNodePtr result = nullptr;
    switch (node.GetAltCase()) {
        case TRule_json_api_expr::kAltJsonApiExpr1: {
            const auto& jsonValue = node.GetAlt_json_api_expr1().GetRule_json_value1();
            pos = GetPos(jsonValue.GetToken1());
            result = JsonValueExpr(jsonValue);
            break;
        }
        case TRule_json_api_expr::kAltJsonApiExpr2: {
            const auto& jsonExists = node.GetAlt_json_api_expr2().GetRule_json_exists1();
            pos = GetPos(jsonExists.GetToken1());
            result = JsonExistsExpr(jsonExists);
            break;
        }
        case TRule_json_api_expr::kAltJsonApiExpr3: {
            const auto& jsonQuery = node.GetAlt_json_api_expr3().GetRule_json_query1();
            pos = GetPos(jsonQuery.GetToken1());
            result = JsonQueryExpr(jsonQuery);
            break;
        }
        case TRule_json_api_expr::ALT_NOT_SET:
            Y_ABORT("You should change implementation according to grammar changes");
    }

    return result;
}

TNodePtr TSqlExpression::RowPatternVarAccess(TString var, const TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2 block) {
    switch (block.GetAltCase()) {
        case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::kAlt1:
            break;
        case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::kAlt2:
            break;
        case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::kAlt3:
            switch (block.GetAlt3().GetRule_an_id_or_type1().GetAltCase()) {
                case TRule_an_id_or_type::kAltAnIdOrType1: {
                    const auto &idOrType = block.GetAlt3().GetRule_an_id_or_type1().GetAlt_an_id_or_type1().GetRule_id_or_type1();
                    switch(idOrType.GetAltCase()) {
                        case TRule_id_or_type::kAltIdOrType1: {
                            const auto column = Id(idOrType.GetAlt_id_or_type1().GetRule_id1(), *this);
                            return BuildMatchRecognizeColumnAccess(Ctx.Pos(), std::move(var), std::move(column));
                        }
                        case TRule_id_or_type::kAltIdOrType2:
                            break;
                        case TRule_id_or_type::ALT_NOT_SET:
                            break;
                    }
                    break;
                }
                case TRule_an_id_or_type::kAltAnIdOrType2:
                    break;
                case TRule_an_id_or_type::ALT_NOT_SET:
                    break;
            }
            break;
        case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::ALT_NOT_SET:
            Y_ABORT("You should change implementation according to grammar changes");
    }
    return {};
}

template<typename TUnaryCasualExprRule>
TNodePtr TSqlExpression::UnaryCasualExpr(const TUnaryCasualExprRule& node, const TTrailingQuestions& tail) {
    // unary_casual_subexpr: (id_expr | atom_expr) unary_subexpr_suffix;
    // OR
    // in_unary_casual_subexpr: (id_expr_in | in_atom_expr) unary_subexpr_suffix;
    // where
    // unary_subexpr_suffix: (key_expr | invoke_expr |(DOT (bind_parameter | DIGITS | id)))* (COLLATE id)?;

    const auto& suffix = node.GetRule_unary_subexpr_suffix2();
    const bool suffixIsEmpty = suffix.GetBlock1().empty() && !suffix.HasBlock2();
    MaybeUnnamedSmartParenOnTop = MaybeUnnamedSmartParenOnTop && suffixIsEmpty;
    TString name;
    TNodePtr expr;
    bool typePossible = false;
    auto& block = node.GetBlock1();
    switch (block.Alt_case()) {
        case TUnaryCasualExprRule::TBlock1::kAlt1: {
            MaybeUnnamedSmartParenOnTop = false;
            auto& alt = block.GetAlt1();
            if constexpr (std::is_same_v<TUnaryCasualExprRule, TRule_unary_casual_subexpr>) {
                name = Id(alt.GetRule_id_expr1(), *this);
                typePossible = !IsQuotedId(alt.GetRule_id_expr1(), *this);
            } else {
                // type was never possible here
                name = Id(alt.GetRule_id_expr_in1(), *this);
            }
            break;
        }
        case TUnaryCasualExprRule::TBlock1::kAlt2: {
            auto& alt = block.GetAlt2();
            TMaybe<TExprOrIdent> exprOrId;
            if constexpr (std::is_same_v<TUnaryCasualExprRule, TRule_unary_casual_subexpr>) {
                exprOrId = AtomExpr(alt.GetRule_atom_expr1(), suffixIsEmpty ? tail : TTrailingQuestions{});
            } else {
                MaybeUnnamedSmartParenOnTop = false;
                exprOrId = InAtomExpr(alt.GetRule_in_atom_expr1(), suffixIsEmpty ? tail : TTrailingQuestions{});
            }

            if (!exprOrId) {
                Ctx.IncrementMonCounter("sql_errors", "BadAtomExpr");
                return nullptr;
            }
            if (!exprOrId->Expr) {
                name = exprOrId->Ident;
            } else {
                expr = exprOrId->Expr;
            }
            break;
        }
        case TUnaryCasualExprRule::TBlock1::ALT_NOT_SET:
            Y_ABORT("You should change implementation according to grammar changes");
    }

    // bool onlyDots = true;
    bool isColumnRef = !expr;
    bool isFirstElem = true;

    for (auto& _b : suffix.GetBlock1()) {
        auto& b = _b.GetBlock1();
        switch (b.Alt_case()) {
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::kAlt1: {
            // key_expr
            // onlyDots = false;
            break;
        }
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::kAlt2: {
            // invoke_expr - cannot be a column, function name
            if (isFirstElem) {
                isColumnRef = false;
            }

            // onlyDots = false;
            break;
        }
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::kAlt3: {
            // In case of MATCH_RECOGNIZE lambdas
            // X.Y is treated as Var.Column access
            if (isColumnRef && (
                EColumnRefState::MatchRecognizeMeasures == Ctx.GetColumnReferenceState() ||
                EColumnRefState::MatchRecognizeDefine == Ctx.GetColumnReferenceState() ||
                EColumnRefState::MatchRecognizeDefineAggregate == Ctx.GetColumnReferenceState()
            )) {
                return RowPatternVarAccess(std::move(name), b.GetAlt3().GetBlock2());
            }
            break;
        }
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::ALT_NOT_SET:
            AltNotImplemented("unary_subexpr_suffix", b);
            return nullptr;
        }

        isFirstElem = false;
    }

    isFirstElem = true;
    TVector<INode::TIdPart> ids;
    INode::TPtr lastExpr;
    if (!isColumnRef) {
        lastExpr = expr;
    } else {
        const bool flexibleTypes = Ctx.FlexibleTypes;
        bool columnOrType = false;
        auto columnRefsState = Ctx.GetColumnReferenceState();
        bool explicitPgType = columnRefsState == EColumnRefState::AsPgType;
        if (explicitPgType && typePossible && suffixIsEmpty) {
            auto pgType = BuildSimpleType(Ctx, Ctx.Pos(), name, false);
            if (pgType && tail.Count) {
                Ctx.Error() << "Optional types are not supported in this context";
                return {};
            }
            return pgType;
        }
        if (auto simpleType = LookupSimpleType(name, flexibleTypes, false); simpleType && typePossible && suffixIsEmpty) {
            if (tail.Count > 0 || columnRefsState == EColumnRefState::Deny || !flexibleTypes) {
                // a type
                return AddOptionals(BuildSimpleType(Ctx, Ctx.Pos(), name, false), tail.Count);
            }
            // type or column: ambiguity will be resolved on type annotation stage
            columnOrType = columnRefsState == EColumnRefState::Allow;
        }
        if (tail.Count) {
            UnexpectedQuestionToken(tail);
            return {};
        }
        if (!Ctx.CheckColumnReference(Ctx.Pos(), name)) {
            return nullptr;
        }

        ids.push_back(columnOrType ? BuildColumnOrType(Ctx.Pos()) : BuildColumn(Ctx.Pos()));
        ids.push_back(name);
    }

    TPosition pos(Ctx.Pos());
    for (auto& _b : suffix.GetBlock1()) {
        auto& b = _b.GetBlock1();
        switch (b.Alt_case()) {
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::kAlt1: {
            // key_expr
            auto keyExpr = KeyExpr(b.GetAlt1().GetRule_key_expr1());
            if (!keyExpr) {
                Ctx.IncrementMonCounter("sql_errors", "BadKeyExpr");
                return nullptr;
            }

            if (!lastExpr) {
                lastExpr = BuildAccess(pos, ids, false);
                ids.clear();
            }

            ids.push_back(lastExpr);
            ids.push_back(keyExpr);
            lastExpr = BuildAccess(pos, ids, true);
            ids.clear();
            break;
        }
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::kAlt2: {
            // invoke_expr - cannot be a column, function name
            TSqlCallExpr call(Ctx, Mode);
            if (isFirstElem && !name.empty()) {
                call.AllowDistinct();
                call.InitName(name);
            } else {
                call.InitExpr(lastExpr);
            }

            bool initRet = call.Init(b.GetAlt2().GetRule_invoke_expr1());
            if (initRet) {
                call.IncCounters();
            }

            if (!initRet) {
                return nullptr;
            }

            lastExpr = call.BuildCall();
            if (!lastExpr) {
                return nullptr;
            }

            break;
        }
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::kAlt3: {
            // dot
            if (lastExpr) {
                ids.push_back(lastExpr);
            }

            auto bb = b.GetAlt3().GetBlock2();
            switch (bb.Alt_case()) {
                case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::kAlt1: {
                    TString named;
                    if (!NamedNodeImpl(bb.GetAlt1().GetRule_bind_parameter1(), named, *this)) {
                        return nullptr;
                    }
                    auto namedNode = GetNamedNode(named);
                    if (!namedNode) {
                        return nullptr;
                    }

                    ids.push_back(named);
                    ids.back().Expr = namedNode;
                    break;
                }
                case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::kAlt2: {
                    const TString str(Token(bb.GetAlt2().GetToken1()));
                    ids.push_back(str);
                    break;
                }
                case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::kAlt3: {
                    ids.push_back(Id(bb.GetAlt3().GetRule_an_id_or_type1(), *this));
                    break;
                }
                case TRule_unary_subexpr_suffix_TBlock1_TBlock1_TAlt3_TBlock2::ALT_NOT_SET:
                    Y_ABORT("You should change implementation according to grammar changes");
            }

            if (lastExpr) {
                lastExpr = BuildAccess(pos, ids, false);
                ids.clear();
            }

            break;
        }
        case TRule_unary_subexpr_suffix::TBlock1::TBlock1::ALT_NOT_SET:
            AltNotImplemented("unary_subexpr_suffix", b);
            return nullptr;
        }

        isFirstElem = false;
    }

    if (!lastExpr) {
        lastExpr = BuildAccess(pos, ids, false);
        ids.clear();
    }

    if (suffix.HasBlock2()) {
        Ctx.IncrementMonCounter("sql_errors", "CollateUnarySubexpr");
        Error() << "unary_subexpr: COLLATE is not implemented yet";
    }

    return lastExpr;
}

TNodePtr TSqlExpression::BindParameterRule(const TRule_bind_parameter& rule, const TTrailingQuestions& tail) {
    TString namedArg;
    if (!NamedNodeImpl(rule, namedArg, *this)) {
        return {};
    }
    if (SmartParenthesisMode == ESmartParenthesis::SqlLambdaParams) {
        Ctx.IncrementMonCounter("sql_features", "LambdaArgument");
        if (tail.Count > 1) {
            Ctx.Error(tail.Pos) << "Expecting at most one '?' token here (for optional lambda parameters), but got " << tail.Count;
            return {};
        }
        return BuildAtom(Ctx.Pos(), namedArg, NYql::TNodeFlags::ArbitraryContent, tail.Count != 0);
    }
    if (tail.Count) {
        UnexpectedQuestionToken(tail);
        return {};
    }
    Ctx.IncrementMonCounter("sql_features", "NamedNodeUseAtom");
    auto ret = GetNamedNode(namedArg);
    if (ret) {
        ret->SetRefPos(Ctx.Pos());
    }

    return ret;
}

TNodePtr TSqlExpression::LambdaRule(const TRule_lambda& rule) {
    const auto& alt = rule;
    const bool isSqlLambda = alt.HasBlock2();
    if (!isSqlLambda) {
        return SmartParenthesis(alt.GetRule_smart_parenthesis1());
    }

    MaybeUnnamedSmartParenOnTop = false;
    TNodePtr parenthesis;
    {
        // we allow column reference here to postpone error and report it with better description in SqlLambdaParams
        TColumnRefScope scope(Ctx, EColumnRefState::Allow);
        TSqlExpression expr(Ctx, Mode);
        expr.SetSmartParenthesisMode(ESmartParenthesis::SqlLambdaParams);
        parenthesis = expr.SmartParenthesis(alt.GetRule_smart_parenthesis1());
    }
    if (!parenthesis) {
        return {};
    }

    ui32 optionalArgumentsCount = 0;
    TVector<TSymbolNameWithPos> args;
    if (!SqlLambdaParams(parenthesis, args, optionalArgumentsCount)) {
        return {};
    }
    auto bodyBlock = alt.GetBlock2();
    Token(bodyBlock.GetToken1());
    TPosition pos(Ctx.Pos());
    TVector<TNodePtr> exprSeq;
    for (auto& arg: args) {
        arg.Name = PushNamedAtom(arg.Pos, arg.Name);
    }
    bool ret = false;
    TColumnRefScope scope(Ctx, EColumnRefState::Deny);
    scope.SetNoColumnErrContext("in lambda function");
    if (bodyBlock.GetBlock2().HasAlt1()) {
        ret = SqlLambdaExprBody(Ctx, bodyBlock.GetBlock2().GetAlt1().GetRule_expr2(), exprSeq);
    } else {
        ret = SqlLambdaExprBody(Ctx, bodyBlock.GetBlock2().GetAlt2().GetRule_lambda_body2(), exprSeq);
    }

    TVector<TString> argNames;
    for (const auto& arg : args) {
        argNames.push_back(arg.Name);
        PopNamedNode(arg.Name);
    }
    if (!ret) {
        return {};
    }

    auto lambdaNode = BuildSqlLambda(pos, std::move(argNames), std::move(exprSeq));
    if (optionalArgumentsCount > 0) {
        lambdaNode = new TCallNodeImpl(pos, "WithOptionalArgs", {
            lambdaNode,
            BuildQuotedAtom(pos, ToString(optionalArgumentsCount), TNodeFlags::Default)
            });
    }

    return lambdaNode;
}

TNodePtr TSqlExpression::CastRule(const TRule_cast_expr& rule) {
    Ctx.IncrementMonCounter("sql_features", "Cast");
    const auto& alt = rule;
    Token(alt.GetToken1());
    TPosition pos(Ctx.Pos());
    TSqlExpression expr(Ctx, Mode);
    auto exprNode = expr.Build(rule.GetRule_expr3());
    if (!exprNode) {
        return {};
    }
    auto type = TypeNodeOrBind(rule.GetRule_type_name_or_bind5());
    if (!type) {
        return {};
    }
    return new TCallNodeImpl(pos, "SafeCast", {exprNode, type});
}

TNodePtr TSqlExpression::BitCastRule(const TRule_bitcast_expr& rule) {
    Ctx.IncrementMonCounter("sql_features", "BitCast");
    const auto& alt = rule;
    Token(alt.GetToken1());
    TPosition pos(Ctx.Pos());
    TSqlExpression expr(Ctx, Mode);
    auto exprNode = expr.Build(rule.GetRule_expr3());
    if (!exprNode) {
        return {};
    }
    auto type = TypeSimple(rule.GetRule_type_name_simple5(), true);
    if (!type) {
        return {};
    }
    return new TCallNodeImpl(pos, "BitCast", {exprNode, type});
}

TNodePtr TSqlExpression::ExistsRule(const TRule_exists_expr& rule) {
    Ctx.IncrementMonCounter("sql_features", "Exists");

    TPosition pos;
    TSourcePtr source;
    Token(rule.GetToken2());
    switch (rule.GetBlock3().Alt_case()) {
        case TRule_exists_expr::TBlock3::kAlt1: {
            const auto& alt = rule.GetBlock3().GetAlt1().GetRule_select_stmt1();
            TSqlSelect select(Ctx, Mode);
            source = select.Build(alt, pos);
            break;
        }
        case TRule_exists_expr::TBlock3::kAlt2: {
            const auto& alt = rule.GetBlock3().GetAlt2().GetRule_values_stmt1();
            TSqlValues values(Ctx, Mode);
            source = values.Build(alt, pos);
            break;
        }
        case TRule_exists_expr::TBlock3::ALT_NOT_SET:
            AltNotImplemented("exists_expr", rule.GetBlock3());
    }

    if (!source) {
        Ctx.IncrementMonCounter("sql_errors", "BadSource");
        return nullptr;
    }
    const bool checkExist = true;
    auto select = BuildSourceNode(Ctx.Pos(), source, checkExist);
    if (Ctx.Settings.EmitReadsForExists) {
        TTableList tableList;
        source->GetInputTables(tableList);

        TNodePtr inputTables(BuildInputTables(Ctx.Pos(), tableList, false, Ctx.Scoped));
        if (!inputTables->Init(Ctx, source.Get())) {
            return nullptr;
        }

        auto node = inputTables;
        node = node->L(node, node->Y("return", select));
        select = node->Y("block", node->Q(node));
    }

    return BuildBuiltinFunc(Ctx, Ctx.Pos(), "ListHasItems", {select});
}

TNodePtr TSqlExpression::CaseRule(const TRule_case_expr& rule) {
    // case_expr: CASE expr? when_expr+ (ELSE expr)? END;
    // when_expr: WHEN expr THEN expr;
    Ctx.IncrementMonCounter("sql_features", "Case");
    const auto& alt = rule;
    Token(alt.GetToken1());
    TNodePtr elseExpr;
    if (alt.HasBlock4()) {
        Token(alt.GetBlock4().GetToken1());
        TSqlExpression expr(Ctx, Mode);
        elseExpr = expr.Build(alt.GetBlock4().GetRule_expr2());
    } else {
        Ctx.IncrementMonCounter("sql_errors", "ElseIsRequired");
        Error() << "ELSE is required";
        return {};
    }

    TNodePtr caseExpr;
    if (alt.HasBlock2()) {
        TSqlExpression expr(Ctx, Mode);
        caseExpr = expr.Build(alt.GetBlock2().GetRule_expr1());
        if (!caseExpr) {
            return {};
        }
    }

    TVector<TCaseBranch> branches;
    for (size_t i = 0; i < alt.Block3Size(); ++i) {
        branches.emplace_back();
        const auto& block = alt.GetBlock3(i).GetRule_when_expr1();
        Token(block.GetToken1());
        TSqlExpression condExpr(Ctx, Mode);
        branches.back().Pred = condExpr.Build(block.GetRule_expr2());
        if (caseExpr) {
            branches.back().Pred = BuildBinaryOp(Ctx, Ctx.Pos(), "==", caseExpr->Clone(), branches.back().Pred);
        }
        if (!branches.back().Pred) {
            return {};
        }
        Token(block.GetToken3());
        TSqlExpression thenExpr(Ctx, Mode);
        branches.back().Value = thenExpr.Build(block.GetRule_expr4());
        if (!branches.back().Value) {
            return {};
        }
    }
    auto final = ReduceCaseBranches(branches.begin(), branches.end());
    return BuildBuiltinFunc(Ctx, Ctx.Pos(), "If", { final.Pred, final.Value, elseExpr });
}

TMaybe<TExprOrIdent> TSqlExpression::AtomExpr(const TRule_atom_expr& node, const TTrailingQuestions& tail) {
    // atom_expr:
    //     literal_value
    //   | bind_parameter
    //   | lambda
    //   | cast_expr
    //   | exists_expr
    //   | case_expr
    //   | an_id_or_type NAMESPACE (id_or_type | STRING_VALUE)
    //   | value_constructor
    //   | bitcast_expr
    //   | list_literal
    //   | dict_literal
    //   | struct_literal
    // ;
    if (node.Alt_case() != TRule_atom_expr::kAltAtomExpr2 && tail.Count) {
        UnexpectedQuestionToken(tail);
        return {};
    }
    MaybeUnnamedSmartParenOnTop = MaybeUnnamedSmartParenOnTop && (node.Alt_case() == TRule_atom_expr::kAltAtomExpr3);
    TExprOrIdent result;
    switch (node.Alt_case()) {
        case TRule_atom_expr::kAltAtomExpr1:
            Ctx.IncrementMonCounter("sql_features", "LiteralExpr");
            return LiteralExpr(node.GetAlt_atom_expr1().GetRule_literal_value1());
        case TRule_atom_expr::kAltAtomExpr2:
            result.Expr = BindParameterRule(node.GetAlt_atom_expr2().GetRule_bind_parameter1(), tail);
            break;
        case TRule_atom_expr::kAltAtomExpr3:
            result.Expr = LambdaRule(node.GetAlt_atom_expr3().GetRule_lambda1());
            break;
        case TRule_atom_expr::kAltAtomExpr4:
            result.Expr = CastRule(node.GetAlt_atom_expr4().GetRule_cast_expr1());
            break;
        case TRule_atom_expr::kAltAtomExpr5:
            result.Expr = ExistsRule(node.GetAlt_atom_expr5().GetRule_exists_expr1());
            break;
        case TRule_atom_expr::kAltAtomExpr6:
            result.Expr = CaseRule(node.GetAlt_atom_expr6().GetRule_case_expr1());
            break;
        case TRule_atom_expr::kAltAtomExpr7: {
            const auto& alt = node.GetAlt_atom_expr7();
            TString module(Id(alt.GetRule_an_id_or_type1(), *this));
            TPosition pos(Ctx.Pos());
            TString name;
            switch (alt.GetBlock3().Alt_case()) {
                case TRule_atom_expr::TAlt7::TBlock3::kAlt1:
                    name = Id(alt.GetBlock3().GetAlt1().GetRule_id_or_type1(), *this);
                    break;
                case TRule_atom_expr::TAlt7::TBlock3::kAlt2: {
                    name = Token(alt.GetBlock3().GetAlt2().GetToken1());
                    if (Ctx.AnsiQuotedIdentifiers && name.StartsWith('"')) {
                        // same as previous case
                        name = IdContentFromString(Ctx, name);
                    } else {
                        module = "@" + module;
                    }
                    break;
                }
                case TRule_atom_expr::TAlt7::TBlock3::ALT_NOT_SET:
                    Y_ABORT("Unsigned number: you should change implementation according to grammar changes");
            }
            result.Expr = BuildCallable(pos, module, name, {});
            break;
        }
        case TRule_atom_expr::kAltAtomExpr8: {
            result.Expr = ValueConstructor(node.GetAlt_atom_expr8().GetRule_value_constructor1());
            break;
        }
        case TRule_atom_expr::kAltAtomExpr9:
            result.Expr = BitCastRule(node.GetAlt_atom_expr9().GetRule_bitcast_expr1());
            break;
        case TRule_atom_expr::kAltAtomExpr10:
            result.Expr = ListLiteral(node.GetAlt_atom_expr10().GetRule_list_literal1());
            break;
        case TRule_atom_expr::kAltAtomExpr11:
            result.Expr = DictLiteral(node.GetAlt_atom_expr11().GetRule_dict_literal1());
            break;
        case TRule_atom_expr::kAltAtomExpr12:
            result.Expr = StructLiteral(node.GetAlt_atom_expr12().GetRule_struct_literal1());
            break;
        case TRule_atom_expr::ALT_NOT_SET:
            AltNotImplemented("atom_expr", node);
    }
    if (!result.Expr) {
        return {};
    }
    return result;
}

TMaybe<TExprOrIdent> TSqlExpression::InAtomExpr(const TRule_in_atom_expr& node, const TTrailingQuestions& tail) {
    // in_atom_expr:
    //     literal_value
    //   | bind_parameter
    //   | lambda
    //   | cast_expr
    //   | case_expr
    //   | an_id_or_type NAMESPACE (id_or_type | STRING_VALUE)
    //   | LPAREN select_stmt RPAREN
    //   | value_constructor
    //   | bitcast_expr
    //   | list_literal
    //   | dict_literal
    //   | struct_literal
    // ;
    if (node.Alt_case() != TRule_in_atom_expr::kAltInAtomExpr2 && tail.Count) {
        UnexpectedQuestionToken(tail);
        return {};
    }
    TExprOrIdent result;
    switch (node.Alt_case()) {
        case TRule_in_atom_expr::kAltInAtomExpr1:
            Ctx.IncrementMonCounter("sql_features", "LiteralExpr");
            return LiteralExpr(node.GetAlt_in_atom_expr1().GetRule_literal_value1());
        case TRule_in_atom_expr::kAltInAtomExpr2:
            result.Expr = BindParameterRule(node.GetAlt_in_atom_expr2().GetRule_bind_parameter1(), tail);
            break;
        case TRule_in_atom_expr::kAltInAtomExpr3:
            result.Expr = LambdaRule(node.GetAlt_in_atom_expr3().GetRule_lambda1());
            break;
        case TRule_in_atom_expr::kAltInAtomExpr4:
            result.Expr = CastRule(node.GetAlt_in_atom_expr4().GetRule_cast_expr1());
            break;
        case TRule_in_atom_expr::kAltInAtomExpr5:
            result.Expr = CaseRule(node.GetAlt_in_atom_expr5().GetRule_case_expr1());
            break;
        case TRule_in_atom_expr::kAltInAtomExpr6: {
            const auto& alt = node.GetAlt_in_atom_expr6();
            TString module(Id(alt.GetRule_an_id_or_type1(), *this));
            TPosition pos(Ctx.Pos());
            TString name;
            switch (alt.GetBlock3().Alt_case()) {
            case TRule_in_atom_expr::TAlt6::TBlock3::kAlt1:
                name = Id(alt.GetBlock3().GetAlt1().GetRule_id_or_type1(), *this);
                break;
            case TRule_in_atom_expr::TAlt6::TBlock3::kAlt2: {
                name = Token(alt.GetBlock3().GetAlt2().GetToken1());
                if (Ctx.AnsiQuotedIdentifiers && name.StartsWith('"')) {
                    // same as previous case
                    name = IdContentFromString(Ctx, name);
                } else {
                    module = "@" + module;
                }
                break;
            }
            case TRule_in_atom_expr::TAlt6::TBlock3::ALT_NOT_SET:
                Y_ABORT("You should change implementation according to grammar changes");
            }
            result.Expr = BuildCallable(pos, module, name, {});
            break;
        }
        case TRule_in_atom_expr::kAltInAtomExpr7: {
            Token(node.GetAlt_in_atom_expr7().GetToken1());
            // reset column reference scope (select will reenable it where needed)
            TColumnRefScope scope(Ctx, EColumnRefState::Deny);
            TSqlSelect select(Ctx, Mode);
            TPosition pos;
            auto source = select.Build(node.GetAlt_in_atom_expr7().GetRule_select_stmt2(), pos);
            if (!source) {
                Ctx.IncrementMonCounter("sql_errors", "BadSource");
                return {};
            }
            Ctx.IncrementMonCounter("sql_features", "InSubquery");
            const auto alias = Ctx.MakeName("subquerynode");
            const auto ref = Ctx.MakeName("subquery");
            auto& blocks = Ctx.GetCurrentBlocks();
            blocks.push_back(BuildSubquery(std::move(source), alias, Mode == NSQLTranslation::ESqlMode::SUBQUERY, -1, Ctx.Scoped));
            blocks.back()->SetLabel(ref);
            result.Expr = BuildSubqueryRef(blocks.back(), ref, -1);
            break;
        }
        case TRule_in_atom_expr::kAltInAtomExpr8: {
            result.Expr = ValueConstructor(node.GetAlt_in_atom_expr8().GetRule_value_constructor1());
            break;
        }
        case TRule_in_atom_expr::kAltInAtomExpr9:
            result.Expr = BitCastRule(node.GetAlt_in_atom_expr9().GetRule_bitcast_expr1());
            break;
        case TRule_in_atom_expr::kAltInAtomExpr10:
            result.Expr = ListLiteral(node.GetAlt_in_atom_expr10().GetRule_list_literal1());
            break;
        case TRule_in_atom_expr::kAltInAtomExpr11:
            result.Expr = DictLiteral(node.GetAlt_in_atom_expr11().GetRule_dict_literal1());
            break;
        case TRule_in_atom_expr::kAltInAtomExpr12:
            result.Expr = StructLiteral(node.GetAlt_in_atom_expr12().GetRule_struct_literal1());
            break;
        case TRule_in_atom_expr::ALT_NOT_SET:
            AltNotImplemented("in_atom_expr", node);
    }
    if (!result.Expr) {
        return {};
    }
    return result;
}

bool TSqlExpression::SqlLambdaParams(const TNodePtr& node, TVector<TSymbolNameWithPos>& args, ui32& optionalArgumentsCount) {
    args.clear();
    optionalArgumentsCount = 0;
    auto errMsg = TStringBuf("Invalid lambda arguments syntax. Lambda arguments should start with '$' as named value.");
    auto tupleNodePtr = node->GetTupleNode();;
    if (!tupleNodePtr) {
        Ctx.Error(node->GetPos()) << errMsg;
        return false;
    }
    THashSet<TString> dupArgsChecker;
    for (const auto& argPtr: tupleNodePtr->Elements()) {
        auto contentPtr = argPtr->GetAtomContent();
        if (!contentPtr || !contentPtr->StartsWith("$")) {
            Ctx.Error(argPtr->GetPos()) << errMsg;
            return false;
        }
        if (argPtr->IsOptionalArg()) {
            ++optionalArgumentsCount;
        } else if (optionalArgumentsCount > 0) {
            Ctx.Error(argPtr->GetPos()) << "Non-optional argument can not follow optional one";
            return false;
        }

        if (!IsAnonymousName(*contentPtr) && !dupArgsChecker.insert(*contentPtr).second) {
            Ctx.Error(argPtr->GetPos()) << "Duplicate lambda argument parametr: '" << *contentPtr << "'.";
            return false;
        }
        args.push_back(TSymbolNameWithPos{*contentPtr, argPtr->GetPos()});
    }
    return true;
}

bool TSqlExpression::SqlLambdaExprBody(TContext& ctx, const TRule_expr& node, TVector<TNodePtr>& exprSeq) {
    TSqlExpression expr(ctx, ctx.Settings.Mode);
    TNodePtr nodeExpr = expr.Build(node);
    if (!nodeExpr) {
        return false;
    }
    exprSeq.push_back(nodeExpr);
    return true;
}

bool TSqlExpression::SqlLambdaExprBody(TContext& ctx, const TRule_lambda_body& node, TVector<TNodePtr>& exprSeq) {
    TSqlExpression expr(ctx, ctx.Settings.Mode);
    TVector<TString> localNames;
    bool hasError = false;
    for (auto& block: node.GetBlock2()) {
        const auto& rule = block.GetRule_lambda_stmt1();
        switch (rule.Alt_case()) {
            case TRule_lambda_stmt::kAltLambdaStmt1: {
                TVector<TSymbolNameWithPos> names;
                auto nodeExpr = NamedNode(rule.GetAlt_lambda_stmt1().GetRule_named_nodes_stmt1(), names);
                if (!nodeExpr) {
                    hasError = true;
                    continue;
                } else if (nodeExpr->GetSource()) {
                    ctx.Error() << "SELECT is not supported inside lambda body";
                    hasError = true;
                    continue;
                }
                if (names.size() > 1) {
                    auto ref = ctx.MakeName("tie");
                    exprSeq.push_back(nodeExpr->Y("EnsureTupleSize", nodeExpr, nodeExpr->Q(ToString(names.size()))));
                    exprSeq.back()->SetLabel(ref);
                    for (size_t i = 0; i < names.size(); ++i) {
                        TNodePtr nthExpr = nodeExpr->Y("Nth", ref, nodeExpr->Q(ToString(i)));
                        names[i].Name = PushNamedAtom(names[i].Pos, names[i].Name);
                        nthExpr->SetLabel(names[i].Name);
                        localNames.push_back(names[i].Name);
                        exprSeq.push_back(nthExpr);
                    }
                } else {
                    auto& symbol = names.front();
                    symbol.Name = PushNamedAtom(symbol.Pos, symbol.Name);
                    nodeExpr->SetLabel(symbol.Name);
                    localNames.push_back(symbol.Name);
                    exprSeq.push_back(nodeExpr);
                }
                break;
            }
            case TRule_lambda_stmt::kAltLambdaStmt2: {
                if (!ImportStatement(rule.GetAlt_lambda_stmt2().GetRule_import_stmt1(), &localNames)) {
                    hasError = true;
                }
                break;
            }
            case TRule_lambda_stmt::ALT_NOT_SET:
                Y_ABORT("SampleClause: does not correspond to grammar changes");
        }
    }

    TNodePtr nodeExpr;
    if (!hasError) {
        nodeExpr = expr.Build(node.GetRule_expr4());
    }

    for (const auto& name : localNames) {
        PopNamedNode(name);
    }

    if (!nodeExpr) {
        return false;
    }
    exprSeq.push_back(nodeExpr);
    return true;
}

TNodePtr TSqlExpression::SubExpr(const TRule_con_subexpr& node, const TTrailingQuestions& tail) {
    // con_subexpr: unary_subexpr | unary_op unary_subexpr;
    switch (node.Alt_case()) {
        case TRule_con_subexpr::kAltConSubexpr1:
            return UnaryExpr(node.GetAlt_con_subexpr1().GetRule_unary_subexpr1(), tail);
        case TRule_con_subexpr::kAltConSubexpr2: {
            MaybeUnnamedSmartParenOnTop = false;
            Ctx.IncrementMonCounter("sql_features", "UnaryOperation");
            TString opName;
            auto token = node.GetAlt_con_subexpr2().GetRule_unary_op1().GetToken1();
            Token(token);
            TPosition pos(Ctx.Pos());
            auto tokenId = token.GetId();
            if (IS_TOKEN(tokenId, NOT)) {
                opName = "Not";
            } else if (IS_TOKEN(tokenId, PLUS)) {
                opName = "Plus";
            } else if (IS_TOKEN(tokenId, MINUS)) {
                opName = Ctx.Scoped->PragmaCheckedOps ? "CheckedMinus" : "Minus";
            } else if (IS_TOKEN(tokenId, TILDA)) {
                opName = "BitNot";
            } else {
                Ctx.IncrementMonCounter("sql_errors", "UnsupportedUnaryOperation");
                Error() << "Unsupported unary operation: " << token.GetValue();
                return nullptr;
            }
            Ctx.IncrementMonCounter("sql_unary_operations", opName);
            auto expr = UnaryExpr(node.GetAlt_con_subexpr2().GetRule_unary_subexpr2(), tail);
            return expr ? expr->ApplyUnaryOp(Ctx, pos, opName) : expr;
        }
        case TRule_con_subexpr::ALT_NOT_SET:
            Y_ABORT("You should change implementation according to grammar changes");
    }
    return nullptr;
}

TNodePtr TSqlExpression::SubExpr(const TRule_xor_subexpr& node, const TTrailingQuestions& tail) {
    // xor_subexpr: eq_subexpr cond_expr?;
    MaybeUnnamedSmartParenOnTop = MaybeUnnamedSmartParenOnTop && !node.HasBlock2();
    TNodePtr res(SubExpr(node.GetRule_eq_subexpr1(), node.HasBlock2() ? TTrailingQuestions{} : tail));
    if (!res) {
        return {};
    }
    TPosition pos(Ctx.Pos());
    if (node.HasBlock2()) {
        auto cond = node.GetBlock2().GetRule_cond_expr1();
        switch (cond.Alt_case()) {
            case TRule_cond_expr::kAltCondExpr1: {
                const auto& matchOp = cond.GetAlt_cond_expr1();
                const bool notMatch = matchOp.HasBlock1();
                const TCiString& opName = Token(matchOp.GetRule_match_op2().GetToken1());
                const auto& pattern = SubExpr(cond.GetAlt_cond_expr1().GetRule_eq_subexpr3(), matchOp.HasBlock4() ? TTrailingQuestions{} : tail);
                if (!pattern) {
                    return {};
                }
                TNodePtr isMatch;
                if (opName == "like" || opName == "ilike") {
                    const TString* escapeLiteral = nullptr;
                    TNodePtr escapeNode;
                    const auto& escaper = BuildUdf(Ctx, pos, "Re2", "PatternFromLike", {});
                    TVector<TNodePtr> escaperArgs({ escaper, pattern });

                    if (matchOp.HasBlock4()) {
                        const auto& escapeBlock = matchOp.GetBlock4();
                        TNodePtr escapeExpr = SubExpr(escapeBlock.GetRule_eq_subexpr2(), tail);
                        if (!escapeExpr) {
                            return {};
                        }
                        escapeLiteral = escapeExpr->GetLiteral("String");
                        escapeNode = escapeExpr;
                        if (escapeLiteral) {
                            Ctx.IncrementMonCounter("sql_features", "LikeEscape");
                            if (escapeLiteral->size() != 1) {
                                Ctx.IncrementMonCounter("sql_errors", "LikeMultiCharEscape");
                                Error() << "ESCAPE clause requires single character argument";
                                return nullptr;
                            }
                            if (escapeLiteral[0] == "%" || escapeLiteral[0] == "_" || escapeLiteral[0] == "\\") {
                                Ctx.IncrementMonCounter("sql_errors", "LikeUnsupportedEscapeChar");
                                Error() << "'%', '_' and '\\' are currently not supported in ESCAPE clause, ";
                                Error() << "please choose any other character";
                                return nullptr;
                            }
                            if (!IsAscii(escapeLiteral->front())) {
                                Ctx.IncrementMonCounter("sql_errors", "LikeUnsupportedEscapeChar");
                                Error() << "Non-ASCII symbols are not supported in ESCAPE clause, ";
                                Error() << "please choose ASCII character";
                                return nullptr;
                            }
                            escaperArgs.push_back(BuildLiteralRawString(pos, *escapeLiteral));
                        } else {
                            Ctx.IncrementMonCounter("sql_errors", "LikeNotLiteralEscape");
                            Error() << "ESCAPE clause requires String literal argument";
                            return nullptr;
                        }
                    }

                    auto re2options = BuildUdf(Ctx, pos, "Re2", "Options", {});
                    if (opName == "ilike") {
                        Ctx.IncrementMonCounter("sql_features", "CaseInsensitiveLike");
                    }
                    auto csModeLiteral = BuildLiteralBool(pos, opName != "ilike");
                    csModeLiteral->SetLabel("CaseSensitive");
                    auto csOption = BuildStructure(pos, { csModeLiteral });
                    auto optionsApply = new TCallNodeImpl(pos, "NamedApply", { re2options, BuildTuple(pos, {}), csOption });

                    const TNodePtr escapedPattern = new TCallNodeImpl(pos, "Apply", { escaperArgs });
                    auto list = new TAstListNodeImpl(pos, { escapedPattern, optionsApply });
                    auto runConfig = new TAstListNodeImpl(pos, { new TAstAtomNodeImpl(pos, "quote", 0), list });

                    const TNodePtr matcher = new TCallNodeImpl(pos, "AssumeStrict", { BuildUdf(Ctx, pos, "Re2", "Match", { runConfig }) });
                    isMatch = new TCallNodeImpl(pos, "Apply", { matcher, res });

                    bool isUtf8 = false;
                    const TString* literalPattern = pattern->GetLiteral("String");
                    if (!literalPattern) {
                        literalPattern = pattern->GetLiteral("Utf8");
                        isUtf8 = literalPattern != nullptr;
                    }

                    if (literalPattern) {
                        bool inEscape = false;
                        TMaybe<char> escape;
                        if (escapeLiteral) {
                            escape = escapeLiteral->front();
                        }

                        bool mayIgnoreCase;
                        TVector<TPatternComponent<char>> components;
                        if (isUtf8) {
                            auto splitResult = SplitPattern(UTF8ToUTF32<false>(*literalPattern), escape, inEscape);
                            for (const auto& component : splitResult) {
                                TPatternComponent<char> converted;
                                converted.IsSimple = component.IsSimple;
                                converted.Prefix = WideToUTF8(component.Prefix);
                                converted.Suffix = WideToUTF8(component.Suffix);
                                components.push_back(std::move(converted));
                            }
                            mayIgnoreCase = ToLowerUTF8(*literalPattern) == ToUpperUTF8(*literalPattern);
                        } else {
                            components = SplitPattern(*literalPattern, escape, inEscape);
                            mayIgnoreCase = WithoutAlpha(*literalPattern);
                        }

                        if (inEscape) {
                            Ctx.IncrementMonCounter("sql_errors", "LikeEscapeSymbolEnd");
                            Error() << "LIKE pattern should not end with escape symbol";
                            return nullptr;
                        }

                        if (opName == "like" || mayIgnoreCase) {
                            // TODO: expand LIKE in optimizers - we can analyze argument types there
                            YQL_ENSURE(!components.empty());
                            const auto& first = components.front();
                            if (components.size() == 1 && first.IsSimple) {
                                // no '%'s and '_'s  in pattern
                                YQL_ENSURE(first.Prefix == first.Suffix);
                                isMatch = BuildBinaryOp(Ctx, pos, "==", res, BuildLiteralRawString(pos, first.Suffix, isUtf8));
                            } else if (!first.Prefix.empty()) {
                                const TString& prefix = first.Prefix;
                                TNodePtr prefixMatch;
                                if (Ctx.EmitStartsWith) {
                                    prefixMatch = BuildBinaryOp(Ctx, pos, "StartsWith", res, BuildLiteralRawString(pos, prefix, isUtf8));
                                } else {
                                    prefixMatch = BuildBinaryOp(Ctx, pos, ">=", res, BuildLiteralRawString(pos, prefix, isUtf8));
                                    auto upperBound = isUtf8 ? NextValidUtf8(prefix) : NextLexicographicString(prefix);
                                    if (upperBound) {
                                        prefixMatch = BuildBinaryOp(
                                            Ctx,
                                            pos,
                                            "And",
                                            prefixMatch,
                                            BuildBinaryOp(Ctx, pos, "<", res, BuildLiteralRawString(pos, TString(*upperBound), isUtf8))
                                        );
                                    }
                                }

                                if (Ctx.AnsiLike && first.IsSimple && components.size() == 2 && components.back().IsSimple) {
                                    const TString& suffix = components.back().Suffix;
                                    // 'prefix%suffix'
                                    if (suffix.empty()) {
                                        isMatch = prefixMatch;
                                    } else {
                                        // len(str) >= len(prefix) + len(suffix) && StartsWith(str, prefix) && EndsWith(str, suffix)
                                        TNodePtr sizePred = BuildBinaryOp(Ctx, pos, ">=",
                                            TNodePtr(new TCallNodeImpl(pos, "Size", { res })),
                                            TNodePtr(new TLiteralNumberNode<ui32>(pos, "Uint32", ToString(prefix.size() + suffix.size()))));
                                        TNodePtr suffixMatch = BuildBinaryOp(Ctx, pos, "EndsWith", res, BuildLiteralRawString(pos, suffix, isUtf8));
                                        isMatch = new TCallNodeImpl(pos, "And", {
                                            sizePred,
                                            prefixMatch,
                                            suffixMatch
                                        });
                                    }
                                } else {
                                    isMatch = BuildBinaryOp(Ctx, pos, "And", prefixMatch, isMatch);
                                }
                            } else if (Ctx.AnsiLike && AllOf(components, [](const auto& comp) { return comp.IsSimple; })) {
                                YQL_ENSURE(first.Prefix.empty());
                                if (components.size() == 3 && components.back().Prefix.empty()) {
                                    // '%foo%'
                                    YQL_ENSURE(!components[1].Prefix.empty());
                                    isMatch = BuildBinaryOp(Ctx, pos, "StringContains", res, BuildLiteralRawString(pos, components[1].Prefix, isUtf8));
                                } else if (components.size() == 2) {
                                    // '%foo'
                                    isMatch = BuildBinaryOp(Ctx, pos, "EndsWith", res, BuildLiteralRawString(pos, components[1].Prefix, isUtf8));
                                }
                            } else if (Ctx.AnsiLike && !components.back().Suffix.empty()) {
                                const TString& suffix = components.back().Suffix;
                                TNodePtr suffixMatch = BuildBinaryOp(Ctx, pos, "EndsWith", res, BuildLiteralRawString(pos, suffix, isUtf8));
                                isMatch = BuildBinaryOp(Ctx, pos, "And", suffixMatch, isMatch);
                            }
                            // TODO: more StringContains/StartsWith/EndsWith cases?
                        }
                    }

                    Ctx.IncrementMonCounter("sql_features", notMatch ? "NotLike" : "Like");

                } else if (opName == "regexp" || opName == "rlike" || opName == "match") {
                    if (matchOp.HasBlock4()) {
                        Ctx.IncrementMonCounter("sql_errors", "RegexpEscape");
                        TString opNameUpper(opName);
                        opNameUpper.to_upper();
                        Error() << opName << " and ESCAPE clauses should not be used together";
                        return nullptr;
                    }

                    if (!Ctx.PragmaRegexUseRe2) {
                        Ctx.Warning(pos, TIssuesIds::CORE_LEGACY_REGEX_ENGINE) << "Legacy regex engine works incorrectly with unicode. Use PRAGMA RegexUseRe2='true';";
                    }

                    const auto& matcher = Ctx.PragmaRegexUseRe2 ?
                        BuildUdf(Ctx, pos, "Re2", opName == "match" ? "Match" : "Grep", {BuildTuple(pos, {pattern, BuildLiteralNull(pos)})}):
                        BuildUdf(Ctx, pos, "Pcre", opName == "match" ? "BacktrackingMatch" : "BacktrackingGrep", { pattern });
                    isMatch = new TCallNodeImpl(pos, "Apply", { matcher, res });
                    if (opName != "match") {
                        Ctx.IncrementMonCounter("sql_features", notMatch ? "NotRegexp" : "Regexp");
                    } else {
                        Ctx.IncrementMonCounter("sql_features", notMatch ? "NotMatch" : "Match");
                    }
                } else {
                    Ctx.IncrementMonCounter("sql_errors", "UnknownMatchOp");
                    AltNotImplemented("match_op", cond);
                    return nullptr;
                }
                return (notMatch && isMatch) ? isMatch->ApplyUnaryOp(Ctx, pos, "Not") : isMatch;
            }
            case TRule_cond_expr::kAltCondExpr2: {
                //   | NOT? IN COMPACT? in_expr
                auto altInExpr = cond.GetAlt_cond_expr2();
                const bool notIn = altInExpr.HasBlock1();
                auto hints = BuildTuple(pos, {});
                bool isCompact = altInExpr.HasBlock3();
                if (!isCompact) {
                    auto sqlHints = Ctx.PullHintForToken(Ctx.TokenPosition(altInExpr.GetToken2()));
                    isCompact = AnyOf(sqlHints, [](const NSQLTranslation::TSQLHint& hint) { return to_lower(hint.Name) == "compact"; });
                }
                if (isCompact) {
                    Ctx.IncrementMonCounter("sql_features", "IsCompactHint");
                    auto sizeHint = BuildTuple(pos, { BuildQuotedAtom(pos, "isCompact", NYql::TNodeFlags::Default) });
                    hints = BuildTuple(pos, { sizeHint });
                }
                TSqlExpression inSubexpr(Ctx, Mode);
                auto inRight = inSubexpr.SqlInExpr(altInExpr.GetRule_in_expr4(), tail);
                auto isIn = BuildBuiltinFunc(Ctx, pos, "In", {res, inRight, hints});
                Ctx.IncrementMonCounter("sql_features", notIn ? "NotIn" : "In");
                return (notIn && isIn) ? isIn->ApplyUnaryOp(Ctx, pos, "Not") : isIn;
            }
            case TRule_cond_expr::kAltCondExpr3: {
                if (tail.Count) {
                    UnexpectedQuestionToken(tail);
                    return {};
                }
                auto altCase = cond.GetAlt_cond_expr3().GetBlock1().Alt_case();
                const bool notNoll =
                    altCase == TRule_cond_expr::TAlt3::TBlock1::kAlt2 ||
                    altCase == TRule_cond_expr::TAlt3::TBlock1::kAlt4
                ;

                if (altCase == TRule_cond_expr::TAlt3::TBlock1::kAlt4 &&
                    !cond.GetAlt_cond_expr3().GetBlock1().GetAlt4().HasBlock1())
                {
                    Ctx.Warning(Ctx.Pos(), TIssuesIds::YQL_MISSING_IS_BEFORE_NOT_NULL) << "Missing IS keyword before NOT NULL";
                }

                auto isNull = BuildIsNullOp(pos, res);
                Ctx.IncrementMonCounter("sql_features", notNoll ? "NotNull" : "Null");
                return (notNoll && isNull) ? isNull->ApplyUnaryOp(Ctx, pos, "Not") : isNull;
            }
            case TRule_cond_expr::kAltCondExpr4: {
                auto alt = cond.GetAlt_cond_expr4();
                const bool symmetric = alt.HasBlock3() && IS_TOKEN(alt.GetBlock3().GetToken1().GetId(), SYMMETRIC);
                const bool negation = alt.HasBlock1();
                TNodePtr left = SubExpr(alt.GetRule_eq_subexpr4(), {});
                TNodePtr right = SubExpr(alt.GetRule_eq_subexpr6(), tail);
                if (!left || !right) {
                    return {};
                }

                const bool bothArgNull = left->IsNull() && right->IsNull();
                const bool oneArgNull  = left->IsNull() || right->IsNull();

                if (res->IsNull() || bothArgNull || (symmetric && oneArgNull)) {
                    Ctx.Warning(pos, TIssuesIds::YQL_OPERATION_WILL_RETURN_NULL)
                    << "BETWEEN operation will return NULL here";
                }

                auto buildSubexpr = [&](const TNodePtr& left, const TNodePtr& right) {
                    if (negation) {
                        return BuildBinaryOpRaw(
                            pos,
                            "Or",
                            BuildBinaryOpRaw(pos, "<", res, left),
                            BuildBinaryOpRaw(pos, ">", res, right)
                        );
                    } else {
                        return BuildBinaryOpRaw(
                            pos,
                            "And",
                            BuildBinaryOpRaw(pos, ">=", res, left),
                            BuildBinaryOpRaw(pos, "<=", res, right)
                        );
                    }
                };

                if (symmetric) {
                    Ctx.IncrementMonCounter("sql_features", negation? "NotBetweenSymmetric" : "BetweenSymmetric");
                    return BuildBinaryOpRaw(
                        pos,
                        negation? "And" : "Or",
                        buildSubexpr(left, right),
                        buildSubexpr(right, left)
                    );
                } else {
                    Ctx.IncrementMonCounter("sql_features", negation? "NotBetween" : "Between");
                    return buildSubexpr(left, right);
                }
            }
            case TRule_cond_expr::kAltCondExpr5: {
                auto alt = cond.GetAlt_cond_expr5();
                auto getNode = [](const TRule_cond_expr::TAlt5::TBlock1& b) -> const TRule_eq_subexpr& { return b.GetRule_eq_subexpr2(); };
                return BinOpList(node.GetRule_eq_subexpr1(), getNode, alt.GetBlock1().begin(), alt.GetBlock1().end(), tail);
            }
            case TRule_cond_expr::ALT_NOT_SET:
                Ctx.IncrementMonCounter("sql_errors", "UnknownConditionExpr");
                AltNotImplemented("cond_expr", cond);
                return nullptr;
        }
    }
    return res;
}

TNodePtr TSqlExpression::BinOperList(const TString& opName, TVector<TNodePtr>::const_iterator begin, TVector<TNodePtr>::const_iterator end) const {
    TPosition pos(Ctx.Pos());
    const size_t opCount = end - begin;
    Y_DEBUG_ABORT_UNLESS(opCount >= 2);
    if (opCount == 2) {
        return BuildBinaryOp(Ctx, pos, opName, *begin, *(begin+1));
    } if (opCount == 3) {
        return BuildBinaryOp(Ctx, pos, opName, BuildBinaryOp(Ctx, pos, opName, *begin, *(begin+1)), *(begin+2));
    } else {
        auto mid = begin + opCount / 2;
        return BuildBinaryOp(Ctx, pos, opName, BinOperList(opName, begin, mid), BinOperList(opName, mid, end));
    }
}

TSqlExpression::TCaseBranch TSqlExpression::ReduceCaseBranches(TVector<TCaseBranch>::const_iterator begin, TVector<TCaseBranch>::const_iterator end) const {
    YQL_ENSURE(begin < end);
    const size_t branchCount = end - begin;
    if (branchCount == 1) {
        return *begin;
    }

    auto mid = begin + branchCount / 2;
    auto left = ReduceCaseBranches(begin, mid);
    auto right = ReduceCaseBranches(mid, end);

    TVector<TNodePtr> preds;
    preds.reserve(branchCount);
    for (auto it = begin; it != end; ++it) {
        preds.push_back(it->Pred);
    }

    TCaseBranch result;
    result.Pred = new TCallNodeImpl(Ctx.Pos(), "Or", CloneContainer(preds));
    result.Value = BuildBuiltinFunc(Ctx, Ctx.Pos(), "If", { left.Pred, left.Value, right.Value });
    return result;
}

template <typename TNode, typename TGetNode, typename TIter>
TNodePtr TSqlExpression::BinOper(const TString& opName, const TNode& node, TGetNode getNode, TIter begin, TIter end, const TTrailingQuestions& tail) {
    if (begin == end) {
        return SubExpr(node, tail);
    }
    // can't have top level smart_parenthesis node if any binary operation is present
    MaybeUnnamedSmartParenOnTop = false;
    Ctx.IncrementMonCounter("sql_binary_operations", opName);
    const size_t listSize = end - begin;
    TVector<TNodePtr> nodes;
    nodes.reserve(1 + listSize);
    nodes.push_back(SubExpr(node, {}));
    for (; begin != end; ++begin) {
        nodes.push_back(SubExpr(getNode(*begin), (begin + 1 == end) ? tail : TTrailingQuestions{}));
    }
    return BinOperList(opName, nodes.begin(), nodes.end());
}

template <typename TNode, typename TGetNode, typename TIter>
TNodePtr TSqlExpression::BinOpList(const TNode& node, TGetNode getNode, TIter begin, TIter end, const TTrailingQuestions& tail) {
    MaybeUnnamedSmartParenOnTop = MaybeUnnamedSmartParenOnTop && (begin == end);
    TNodePtr partialResult = SubExpr(node, (begin == end) ? tail : TTrailingQuestions{});
    while (begin != end) {
        Ctx.IncrementMonCounter("sql_features", "BinaryOperation");
        Token(begin->GetToken1());
        TPosition pos(Ctx.Pos());
        TString opName;
        auto tokenId = begin->GetToken1().GetId();
        if (IS_TOKEN(tokenId, LESS)) {
            opName = "<";
            Ctx.IncrementMonCounter("sql_binary_operations", "Less");
        } else if (IS_TOKEN(tokenId, LESS_OR_EQ)) {
            opName = "<=";
            Ctx.IncrementMonCounter("sql_binary_operations", "LessOrEq");
        } else if (IS_TOKEN(tokenId, GREATER)) {
            opName = ">";
            Ctx.IncrementMonCounter("sql_binary_operations", "Greater");
        } else if (IS_TOKEN(tokenId, GREATER_OR_EQ)) {
            opName = ">=";
            Ctx.IncrementMonCounter("sql_binary_operations", "GreaterOrEq");
        } else if (IS_TOKEN(tokenId, PLUS)) {
            opName = Ctx.Scoped->PragmaCheckedOps ? "CheckedAdd" : "+MayWarn";
            Ctx.IncrementMonCounter("sql_binary_operations", "Plus");
        } else if (IS_TOKEN(tokenId, MINUS)) {
            opName = Ctx.Scoped->PragmaCheckedOps ? "CheckedSub" : "-MayWarn";
            Ctx.IncrementMonCounter("sql_binary_operations", "Minus");
        } else if (IS_TOKEN(tokenId, ASTERISK)) {
            opName = Ctx.Scoped->PragmaCheckedOps ? "CheckedMul" : "*MayWarn";
            Ctx.IncrementMonCounter("sql_binary_operations", "Multiply");
        } else if (IS_TOKEN(tokenId, SLASH)) {
            opName = "/MayWarn";
            Ctx.IncrementMonCounter("sql_binary_operations", "Divide");
            if (!Ctx.Scoped->PragmaClassicDivision && partialResult) {
                partialResult = new TCallNodeImpl(pos, "SafeCast", {std::move(partialResult), BuildDataType(pos, "Double")});
            } else if (Ctx.Scoped->PragmaCheckedOps) {
                opName = "CheckedDiv";
            }
        } else if (IS_TOKEN(tokenId, PERCENT)) {
            opName = Ctx.Scoped->PragmaCheckedOps ? "CheckedMod" : "%MayWarn";
            Ctx.IncrementMonCounter("sql_binary_operations", "Mod");
        } else {
            Ctx.IncrementMonCounter("sql_errors", "UnsupportedBinaryOperation");
            Error() << "Unsupported binary operation token: " << tokenId;
            return nullptr;
        }

        partialResult = BuildBinaryOp(Ctx, pos, opName, partialResult, SubExpr(getNode(*begin), (begin + 1 == end) ? tail : TTrailingQuestions{}));
        ++begin;
    }

    return partialResult;
}

template <typename TGetNode, typename TIter>
TNodePtr TSqlExpression::BinOpList(const TRule_bit_subexpr& node, TGetNode getNode, TIter begin, TIter end, const TTrailingQuestions& tail) {
    MaybeUnnamedSmartParenOnTop = MaybeUnnamedSmartParenOnTop && (begin == end);
    TNodePtr partialResult = SubExpr(node, (begin == end) ? tail : TTrailingQuestions{});
    while (begin != end) {
        Ctx.IncrementMonCounter("sql_features", "BinaryOperation");
        TString opName;
        switch (begin->GetBlock1().Alt_case()) {
            case TRule_neq_subexpr_TBlock2_TBlock1::kAlt1: {
                Token(begin->GetBlock1().GetAlt1().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt1().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, SHIFT_LEFT)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                opName = "ShiftLeft";
                Ctx.IncrementMonCounter("sql_binary_operations", "ShiftLeft");
                break;
            }
            case TRule_neq_subexpr_TBlock2_TBlock1::kAlt2: {
                opName = "ShiftRight";
                Ctx.IncrementMonCounter("sql_binary_operations", "ShiftRight");
                break;
            }
            case TRule_neq_subexpr_TBlock2_TBlock1::kAlt3: {
                Token(begin->GetBlock1().GetAlt3().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt3().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, ROT_LEFT)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                opName = "RotLeft";
                Ctx.IncrementMonCounter("sql_binary_operations", "RotLeft");
                break;
            }
            case TRule_neq_subexpr_TBlock2_TBlock1::kAlt4: {
                opName = "RotRight";
                Ctx.IncrementMonCounter("sql_binary_operations", "RotRight");
                break;
            }
            case TRule_neq_subexpr_TBlock2_TBlock1::kAlt5: {
                Token(begin->GetBlock1().GetAlt5().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt5().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, AMPERSAND)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                opName = "BitAnd";
                Ctx.IncrementMonCounter("sql_binary_operations", "BitAnd");
                break;
            }
            case TRule_neq_subexpr_TBlock2_TBlock1::kAlt6: {
                Token(begin->GetBlock1().GetAlt6().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt6().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, PIPE)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                opName = "BitOr";
                Ctx.IncrementMonCounter("sql_binary_operations", "BitOr");
                break;
            }
            case TRule_neq_subexpr_TBlock2_TBlock1::kAlt7: {
                Token(begin->GetBlock1().GetAlt7().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt7().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, CARET)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                opName = "BitXor";
                Ctx.IncrementMonCounter("sql_binary_operations", "BitXor");
                break;
            }
            case TRule_neq_subexpr_TBlock2_TBlock1::ALT_NOT_SET:
                Y_ABORT("You should change implementation according to grammar changes");
        }

        partialResult = BuildBinaryOp(Ctx, Ctx.Pos(), opName, partialResult, SubExpr(getNode(*begin), (begin + 1 == end) ? tail : TTrailingQuestions{}));
        ++begin;
    }

    return partialResult;
}

template <typename TGetNode, typename TIter>
TNodePtr TSqlExpression::BinOpList(const TRule_eq_subexpr& node, TGetNode getNode, TIter begin, TIter end, const TTrailingQuestions& tail) {
    MaybeUnnamedSmartParenOnTop = MaybeUnnamedSmartParenOnTop && (begin == end);
    TNodePtr partialResult = SubExpr(node, (begin == end) ? tail : TTrailingQuestions{});
    while (begin != end) {
        Ctx.IncrementMonCounter("sql_features", "BinaryOperation");
        TString opName;
        switch (begin->GetBlock1().Alt_case()) {
            case TRule_cond_expr::TAlt5::TBlock1::TBlock1::kAlt1: {
                Token(begin->GetBlock1().GetAlt1().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt1().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, EQUALS)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                Ctx.IncrementMonCounter("sql_binary_operations", "Equals");
                opName = "==";
                break;
            }
            case TRule_cond_expr::TAlt5::TBlock1::TBlock1::kAlt2: {
                Token(begin->GetBlock1().GetAlt2().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt2().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, EQUALS2)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                Ctx.IncrementMonCounter("sql_binary_operations", "Equals2");
                opName = "==";
                break;
            }
            case TRule_cond_expr::TAlt5::TBlock1::TBlock1::kAlt3: {
                Token(begin->GetBlock1().GetAlt3().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt3().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, NOT_EQUALS)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                Ctx.IncrementMonCounter("sql_binary_operations", "NotEquals");
                opName = "!=";
                break;
            }
            case TRule_cond_expr::TAlt5::TBlock1::TBlock1::kAlt4: {
                Token(begin->GetBlock1().GetAlt4().GetToken1());
                auto tokenId = begin->GetBlock1().GetAlt4().GetToken1().GetId();
                if (!IS_TOKEN(tokenId, NOT_EQUALS2)) {
                    Error() << "Unsupported binary operation token: " << tokenId;
                    return {};
                }
                Ctx.IncrementMonCounter("sql_binary_operations", "NotEquals2");
                opName = "!=";
                break;
            }
            case TRule_cond_expr::TAlt5::TBlock1::TBlock1::kAlt5: {
                Token(begin->GetBlock1().GetAlt5().GetRule_distinct_from_op1().GetToken1());
                opName = begin->GetBlock1().GetAlt5().GetRule_distinct_from_op1().HasBlock2() ? "IsNotDistinctFrom" : "IsDistinctFrom";
                Ctx.IncrementMonCounter("sql_binary_operations", opName);
                break;
            }
            case TRule_cond_expr::TAlt5::TBlock1::TBlock1::ALT_NOT_SET:
                Y_ABORT("You should change implementation according to grammar changes");
        }

        partialResult = BuildBinaryOp(Ctx, Ctx.Pos(), opName, partialResult, SubExpr(getNode(*begin), (begin + 1 == end) ? tail : TTrailingQuestions{}));
        ++begin;
    }

    return partialResult;
}

TNodePtr TSqlExpression::SqlInExpr(const TRule_in_expr& node, const TTrailingQuestions& tail) {
    TSqlExpression expr(Ctx, Mode);
    expr.SetSmartParenthesisMode(TSqlExpression::ESmartParenthesis::InStatement);
    auto result = expr.UnaryExpr(node.GetRule_in_unary_subexpr1(), tail);
    return result;
}

TNodePtr TSqlExpression::SmartParenthesis(const TRule_smart_parenthesis& node) {
    TVector<TNodePtr> exprs;
    Token(node.GetToken1());
    const TPosition pos(Ctx.Pos());
    const bool isTuple = node.HasBlock3();
    bool expectTuple = SmartParenthesisMode == ESmartParenthesis::InStatement;
    EExpr mode = EExpr::Regular;
    if (SmartParenthesisMode == ESmartParenthesis::SqlLambdaParams) {
        mode = EExpr::SqlLambdaParams;
        expectTuple = true;
    }
    if (node.HasBlock2() && !NamedExprList(node.GetBlock2().GetRule_named_expr_list1(), exprs, mode)) {
        return {};
    }

    bool topLevelGroupBy = MaybeUnnamedSmartParenOnTop && SmartParenthesisMode == ESmartParenthesis::GroupBy;

    bool hasAliases = false;
    bool hasUnnamed = false;
    for (const auto& expr: exprs) {
        if (expr->GetLabel()) {
            hasAliases = true;
        } else {
            hasUnnamed = true;
        }
        if (hasAliases && hasUnnamed && !topLevelGroupBy) {
            Ctx.IncrementMonCounter("sql_errors", "AnonymousStructMembers");
            Ctx.Error(pos) << "Structure does not allow anonymous members";
            return nullptr;
        }
    }
    if (exprs.size() == 1 && hasUnnamed && !isTuple && !expectTuple) {
        return exprs.back();
    }
    if (topLevelGroupBy) {
        if (isTuple) {
            Ctx.IncrementMonCounter("sql_errors", "SimpleTupleInGroupBy");
            Token(node.GetBlock3().GetToken1());
            Ctx.Error() << "Unexpected trailing comma in grouping elements list";
            return nullptr;
        }
        Ctx.IncrementMonCounter("sql_features", "ListOfNamedNode");
        return BuildListOfNamedNodes(pos, std::move(exprs));
    }
    Ctx.IncrementMonCounter("sql_features", hasUnnamed ? "SimpleTuple" : "SimpleStruct");
    return (hasUnnamed || expectTuple || exprs.size() == 0) ? BuildTuple(pos, exprs) : BuildStructure(pos, exprs);
}

} // namespace NSQLTranslationV1