#include "sql_expression.h" #include "sql_call_expr.h" #include "sql_select.h" #include "sql_values.h" #include #include #include #include #include #include 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 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& 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& 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& 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(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(ctx.Pos(), "Uint64", ToString(value), implicitType); } else if (noSpaceForInt32) { return new TLiteralNumberNode(ctx.Pos(), "Int64", ToString(value), implicitType); } return new TLiteralNumberNode(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(ctx.Pos(), "PgInt8", ToString(value), implicitType); } return new TLiteralNumberNode(ctx.Pos(), "PgInt4", ToString(value), implicitType); } else if (suffix == "u") { return new TLiteralNumberNode(ctx.Pos(), "Uint32", ToString(value)); } else if (suffix == "ul") { return new TLiteralNumberNode(ctx.Pos(), "Uint64", ToString(value)); } else if (suffix == "ut") { return new TLiteralNumberNode(ctx.Pos(), "Uint8", ToString(value)); } else if (suffix == "t") { return new TLiteralNumberNode(ctx.Pos(), "Int8", ToString(value)); } else if (suffix == "l") { return new TLiteralNumberNode(ctx.Pos(), "Int64", ToString(value)); } else if (suffix == "us") { return new TLiteralNumberNode(ctx.Pos(), "Uint16", ToString(value)); } else if (suffix == "s") { return new TLiteralNumberNode(ctx.Pos(), "Int16", ToString(value)); } else if (suffix == "ps") { return new TLiteralNumberNode(ctx.Pos(), "PgInt2", ToString(value)); } else if (suffix == "pi") { return new TLiteralNumberNode(ctx.Pos(), "PgInt4", ToString(value)); } else if (suffix == "pb") { return new TLiteralNumberNode(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(ctx.Pos(), "Float", value.substr(0, value.size()-1)); } else if (lower.EndsWith("p")) { return new TLiteralNumberNode(ctx.Pos(), "PgFloat8", value.substr(0, value.size()-1)); } else if (lower.EndsWith("pf4")) { return new TLiteralNumberNode(ctx.Pos(), "PgFloat4", value.substr(0, value.size()-3)); } else if (lower.EndsWith("pf8")) { return new TLiteralNumberNode(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(ctx.Pos(), "Double", value); } } TMaybe 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(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 TNodePtr TSqlExpression::UnaryExpr(const TUnarySubExprType& node, const TTrailingQuestions& tail) { if constexpr (std::is_same_v) { 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& 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& 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 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& 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& 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 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& 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(mode))); } } TNodePtr TSqlExpression::JsonExistsExpr(const TRule_json_exists& node) { /* json_exists: JSON_EXISTS LPAREN json_common_args json_exists_handler? RPAREN; */ TVector 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 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 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) { 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 exprOrId; if constexpr (std::is_same_v) { 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 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 args; if (!SqlLambdaParams(parenthesis, args, optionalArgumentsCount)) { return {}; } auto bodyBlock = alt.GetBlock2(); Token(bodyBlock.GetToken1()); TPosition pos(Ctx.Pos()); TVector 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 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, Ctx.Settings.EmitReadsForExists); 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 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 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 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& 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 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& 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& exprSeq) { TSqlExpression expr(ctx, ctx.Settings.Mode); TVector 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 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 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 escape; if (escapeLiteral) { escape = escapeLiteral->front(); } bool mayIgnoreCase; TVector> components; if (isUtf8) { auto splitResult = SplitPattern(UTF8ToUTF32(*literalPattern), escape, inEscape); for (const auto& component : splitResult) { TPatternComponent 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(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::const_iterator begin, TVector::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::const_iterator begin, TVector::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 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 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 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 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 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 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 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