#include "yql_expr_constraint.h" #include "yql_callable_transform.h" #include "yql_opt_utils.h" #include #include #include #include #include #include #include #include #include #include namespace NYql { using namespace NNodes; namespace { template struct TApplyConstraintFromInput; template struct TApplyConstraintFromInput { static void Do(const TExprNode::TPtr&) { } }; template struct TApplyConstraintFromInput { static void Do(const TExprNode::TPtr& input) { if (auto c = input->Child(FromChild)->GetConstraint()) { input->AddConstraint(c); } TApplyConstraintFromInput::Do(input); } }; template const TConstraint* MakeCommonConstraint(const TExprNode::TPtr& input, size_t from, TExprContext& ctx) { TVector constraints; for (size_t i = from; i < input->ChildrenSize(); ++i) { constraints.push_back(&input->Child(i)->GetConstraintSet()); } return TConstraint::MakeCommon(constraints, ctx); } template struct TApplyCommonConstraint; template struct TApplyCommonConstraint { static void Do(const TExprNode::TPtr& input, const std::vector& constraints, TExprContext& ctx) { if (auto c = TConstraint::MakeCommon(constraints, ctx)) { input->AddConstraint(c); } } }; template struct TApplyCommonConstraint { static void Do(const TExprNode::TPtr& input, const std::vector& constraints, TExprContext& ctx) { if (auto c = TConstraint::MakeCommon(constraints, ctx)) { input->AddConstraint(c); } TApplyCommonConstraint::Do(input, constraints, ctx); } }; class TCallableConstraintTransformer : public TCallableTransformerBase { using THandler = TStatus(TCallableConstraintTransformer::*)(const TExprNode::TPtr&, TExprNode::TPtr&, TExprContext&) const; public: TCallableConstraintTransformer(TTypeAnnotationContext& types, bool instantOnly, bool subGraph) : TCallableTransformerBase(types, instantOnly) , SubGraph(subGraph) { Functions["Unordered"] = &TCallableConstraintTransformer::FromFirst; Functions["UnorderedSubquery"] = &TCallableConstraintTransformer::FromFirst; Functions["Sort"] = &TCallableConstraintTransformer::SortWrap; Functions["AssumeSorted"] = &TCallableConstraintTransformer::SortWrap; Functions["AssumeUnique"] = &TCallableConstraintTransformer::AssumeUniqueWrap; Functions["AssumeDistinct"] = &TCallableConstraintTransformer::AssumeUniqueWrap; Functions["AssumeUniqueHint"] = &TCallableConstraintTransformer::AssumeUniqueWrap; Functions["AssumeDistinctHint"] = &TCallableConstraintTransformer::AssumeUniqueWrap; Functions["AssumeConstraints"] = &TCallableConstraintTransformer::AssumeConstraintsWrap; Functions["AssumeChopped"] = &TCallableConstraintTransformer::AssumeChoppedWrap; Functions["AssumeColumnOrder"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["AssumeAllMembersNullableAtOnce"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["Top"] = &TCallableConstraintTransformer::TopWrap; Functions["TopSort"] = &TCallableConstraintTransformer::TopWrap; Functions["TakeWhile"] = &TCallableConstraintTransformer::FilterWrap; Functions["SkipWhile"] = &TCallableConstraintTransformer::FilterWrap; Functions["TakeWhileInclusive"] = &TCallableConstraintTransformer::FilterWrap; Functions["SkipWhileInclusive"] = &TCallableConstraintTransformer::FilterWrap; Functions["WideTakeWhile"] = &TCallableConstraintTransformer::FilterWrap; Functions["WideSkipWhile"] = &TCallableConstraintTransformer::FilterWrap; Functions["WideTakeWhileInclusive"] = &TCallableConstraintTransformer::FilterWrap; Functions["WideSkipWhileInclusive"] = &TCallableConstraintTransformer::FilterWrap; Functions["Iterator"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["ForwardList"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["LazyList"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["ToFlow"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["FromFlow"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["ToStream"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["ToSequence"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["Collect"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["FilterNullMembers"] = &TCallableConstraintTransformer::FromFirst; Functions["SkipNullMembers"] = &TCallableConstraintTransformer::FromFirst; Functions["FilterNullElements"] = &TCallableConstraintTransformer::FromFirst; Functions["SkipNullElements"] = &TCallableConstraintTransformer::FromFirst; Functions["Right!"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["Cons!"] = &TCallableConstraintTransformer::CopyAllFrom<1>; Functions["ExtractMembers"] = &TCallableConstraintTransformer::ExtractMembersWrap; Functions["RemoveSystemMembers"] = &TCallableConstraintTransformer::RemovePrefixMembersWrap; Functions["RemovePrefixMembers"] = &TCallableConstraintTransformer::RemovePrefixMembersWrap; Functions["FlattenMembers"] = &TCallableConstraintTransformer::FlattenMembersWrap; Functions["SelectMembers"] = &TCallableConstraintTransformer::SelectMembersWrap; Functions["FilterMembers"] = &TCallableConstraintTransformer::SelectMembersWrap; Functions["CastStruct"] = &TCallableConstraintTransformer::SelectMembersWrap; Functions["SafeCast"] = &TCallableConstraintTransformer::CastWrap; Functions["StrictCast"] = &TCallableConstraintTransformer::CastWrap; Functions["ToString"] = &TCallableConstraintTransformer::CastWrap; Functions["ToBytes"] = &TCallableConstraintTransformer::CastWrap; Functions["DivePrefixMembers"] = &TCallableConstraintTransformer::DivePrefixMembersWrap; Functions["OrderedFilter"] = &TCallableConstraintTransformer::FilterWrap; Functions["Filter"] = &TCallableConstraintTransformer::FilterWrap; Functions["WideFilter"] = &TCallableConstraintTransformer::FilterWrap; Functions["OrderedMap"] = &TCallableConstraintTransformer::MapWrap; Functions["Map"] = &TCallableConstraintTransformer::MapWrap; Functions["MapNext"] = &TCallableConstraintTransformer::MapWrap; Functions["OrderedFlatMap"] = &TCallableConstraintTransformer::MapWrap; Functions["FlatMap"] = &TCallableConstraintTransformer::MapWrap; Functions["OrderedMultiMap"] = &TCallableConstraintTransformer::MapWrap; Functions["MultiMap"] = &TCallableConstraintTransformer::MapWrap; Functions["ExpandMap"] = &TCallableConstraintTransformer::MapWrap; Functions["WideMap"] = &TCallableConstraintTransformer::MapWrap; Functions["NarrowMap"] = &TCallableConstraintTransformer::MapWrap; Functions["NarrowFlatMap"] = &TCallableConstraintTransformer::MapWrap; Functions["NarrowMultiMap"] = &TCallableConstraintTransformer::MapWrap; Functions["OrderedFlatMapToEquiJoin"] = &TCallableConstraintTransformer::MapWrap; Functions["FlatMapToEquiJoin"] = &TCallableConstraintTransformer::MapWrap; Functions["OrderedLMap"] = &TCallableConstraintTransformer::LMapWrap; Functions["LMap"] = &TCallableConstraintTransformer::LMapWrap; Functions["Extract"] = &TCallableConstraintTransformer::FromFirst; Functions["OrderedExtract"] = &TCallableConstraintTransformer::FromFirst; Functions["OrderedExtend"] = &TCallableConstraintTransformer::ExtendWrap; Functions["Extend"] = &TCallableConstraintTransformer::ExtendWrap; Functions["UnionAll"] = &TCallableConstraintTransformer::ExtendWrap; Functions["Merge"] = &TCallableConstraintTransformer::MergeWrap; Functions["UnionMerge"] = &TCallableConstraintTransformer::MergeWrap; Functions["Skip"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["Take"] = &TCallableConstraintTransformer::TakeWrap; Functions["Limit"] = &TCallableConstraintTransformer::TakeWrap; Functions["Member"] = &TCallableConstraintTransformer::MemberWrap; Functions["AsStruct"] = &TCallableConstraintTransformer::AsStructWrap; Functions["BlockAsStruct"] = &TCallableConstraintTransformer::AsStructWrap; Functions["Just"] = &TCallableConstraintTransformer::FromFirst; Functions["Unwrap"] = &TCallableConstraintTransformer::FromFirst; Functions["Ensure"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["ToList"] = &TCallableConstraintTransformer::FromFirst; Functions["ToOptional"] = &TCallableConstraintTransformer::FromFirst; Functions["Head"] = &TCallableConstraintTransformer::FromFirst; Functions["Last"] = &TCallableConstraintTransformer::FromFirst; Functions["Reverse"] = &TCallableConstraintTransformer::ReverseWrap; Functions["Replicate"] = &TCallableConstraintTransformer::FromFirst; Functions["AddMember"] = &TCallableConstraintTransformer::AddMemberWrap; Functions["RemoveMember"] = &TCallableConstraintTransformer::RemoveMemberWrap; Functions["ForceRemoveMember"] = &TCallableConstraintTransformer::RemoveMemberWrap; Functions["ReplaceMember"] = &TCallableConstraintTransformer::ReplaceMemberWrap; Functions["AsList"] = &TCallableConstraintTransformer::AsListWrap; Functions["OptionalIf"] = &TCallableConstraintTransformer::PassOrEmptyWrap; Functions["FlatOptionalIf"] = &TCallableConstraintTransformer::PassOrEmptyWrap; Functions["ListIf"] = &TCallableConstraintTransformer::PassOrEmptyWrap; Functions["FlatListIf"] = &TCallableConstraintTransformer::PassOrEmptyWrap; Functions["EmptyIterator"] = &TCallableConstraintTransformer::FromEmpty; Functions["EmptyFrom"] = &TCallableConstraintTransformer::EmptyFromWrap; Functions["List"] = &TCallableConstraintTransformer::ListWrap; Functions["Dict"] = &TCallableConstraintTransformer::DictWrap; Functions["EmptyList"] = &TCallableConstraintTransformer::FromEmpty; Functions["EmptyDict"] = &TCallableConstraintTransformer::FromEmpty; Functions["DictFromKeys"] = &TCallableConstraintTransformer::DictFromKeysWrap; Functions["If"] = &TCallableConstraintTransformer::IfWrap; Functions["Nothing"] = &TCallableConstraintTransformer::FromEmpty; Functions["IfPresent"] = &TCallableConstraintTransformer::IfPresentWrap; Functions["Coalesce"] = &TCallableConstraintTransformer::CommonFromChildren<0, TSortedConstraintNode, TPartOfSortedConstraintNode, TChoppedConstraintNode, TPartOfChoppedConstraintNode, TEmptyConstraintNode, TUniqueConstraintNode, TPartOfUniqueConstraintNode, TDistinctConstraintNode, TPartOfDistinctConstraintNode, TVarIndexConstraintNode, TMultiConstraintNode>; Functions["CombineByKey"] = &TCallableConstraintTransformer::FromFinalLambda; Functions["FinalizeByKey"] = &TCallableConstraintTransformer::FromFinalLambda; Functions["CombineCore"] = &TCallableConstraintTransformer::FromFinalLambda; Functions["PartitionByKey"] = &TCallableConstraintTransformer::ShuffleByKeysWrap; Functions["PartitionsByKeys"] = &TCallableConstraintTransformer::ShuffleByKeysWrap; Functions["ShuffleByKeys"] = &TCallableConstraintTransformer::ShuffleByKeysWrap; Functions["Switch"] = &TCallableConstraintTransformer::SwitchWrap; Functions["Visit"] = &TCallableConstraintTransformer::VisitWrap; Functions["VariantItem"] = &TCallableConstraintTransformer::VariantItemWrap; Functions["Variant"] = &TCallableConstraintTransformer::VariantWrap; Functions["Guess"] = &TCallableConstraintTransformer::GuessWrap; Functions["Mux"] = &TCallableConstraintTransformer::MuxWrap; Functions["Nth"] = &TCallableConstraintTransformer::NthWrap; Functions["EquiJoin"] = &TCallableConstraintTransformer::EquiJoinWrap; Functions["JoinDict"] = &TCallableConstraintTransformer::JoinDictWrap; Functions["MapJoinCore"] = &TCallableConstraintTransformer::MapJoinCoreWrap; Functions["GraceJoinCore"] = &TCallableConstraintTransformer::GraceJoinCoreWrap; Functions["GraceSelfJoinCore"] = &TCallableConstraintTransformer::GraceSelfJoinCoreWrap; Functions["CommonJoinCore"] = &TCallableConstraintTransformer::FromFirst; Functions["ToDict"] = &TCallableConstraintTransformer::ToDictWrap; Functions["DictItems"] = &TCallableConstraintTransformer::DictItemsWrap; Functions["DictKeys"] = &TCallableConstraintTransformer::DictHalfWrap; Functions["DictPayloads"] = &TCallableConstraintTransformer::DictHalfWrap; Functions["Chain1Map"] = &TCallableConstraintTransformer::Chain1MapWrap; Functions["WideChain1Map"] = &TCallableConstraintTransformer::Chain1MapWrap; Functions["IsKeySwitch"] = &TCallableConstraintTransformer::IsKeySwitchWrap; Functions["Condense"] = &TCallableConstraintTransformer::CondenseWrap; Functions["Condense1"] = &TCallableConstraintTransformer::Condense1Wrap; Functions["GroupingCore"] = &TCallableConstraintTransformer::InheriteEmptyFromInput; Functions["Chopper"] = &TCallableConstraintTransformer::InheriteEmptyFromInput; Functions["WideChopper"] = &TCallableConstraintTransformer::InheriteEmptyFromInput; Functions["WideCombiner"] = &TCallableConstraintTransformer::InheriteEmptyFromInput; Functions["WideCondense1"] = &TCallableConstraintTransformer::Condense1Wrap; Functions["Aggregate"] = &TCallableConstraintTransformer::AggregateWrap; Functions["AggregateMergeState"] = &TCallableConstraintTransformer::AggregateWrap; Functions["AggregateMergeFinalize"] = &TCallableConstraintTransformer::AggregateWrap; Functions["AggregateMergeManyFinalize"] = &TCallableConstraintTransformer::AggregateWrap; Functions["AggregateFinalize"] = &TCallableConstraintTransformer::AggregateWrap; Functions["AggregateCombine"] = &TCallableConstraintTransformer::AggregateWrap; Functions["AggregateCombineState"] = &TCallableConstraintTransformer::AggregateWrap; Functions["Fold"] = &TCallableConstraintTransformer::FoldWrap; Functions["Fold1"] = &TCallableConstraintTransformer::FoldWrap; Functions["WithContext"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["WithWorld"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["WideTop"] = &TCallableConstraintTransformer::WideTopWrap; Functions["WideTopSort"] = &TCallableConstraintTransformer::WideTopWrap; Functions["WideSort"] = &TCallableConstraintTransformer::WideTopWrap; Functions["WideTopBlocks"] = &TCallableConstraintTransformer::WideTopWrap; Functions["WideTopSortBlocks"] = &TCallableConstraintTransformer::WideTopWrap; Functions["WideSortBlocks"] = &TCallableConstraintTransformer::WideTopWrap; Functions["WideToBlocks"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["WideFromBlocks"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["BlockExpandChunked"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["ReplicateScalars"] = &TCallableConstraintTransformer::CopyAllFrom<0>; Functions["BlockMergeFinalizeHashed"] = &TCallableConstraintTransformer::AggregateWrap; Functions["BlockMergeManyFinalizeHashed"] = &TCallableConstraintTransformer::AggregateWrap; Functions["MultiHoppingCore"] = &TCallableConstraintTransformer::MultiHoppingCoreWrap; Functions["StablePickle"] = &TCallableConstraintTransformer::FromFirst; Functions["Unpickle"] = &TCallableConstraintTransformer::FromSecond; } std::optional ProcessCore(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) { if (const auto func = Functions.find(input->Content()); Functions.cend() != func) { return (this->*func->second)(input, output, ctx); } return std::nullopt; } std::optional ProcessList(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) { if (!input->ChildrenSize() || ETypeAnnotationKind::Tuple != input->GetTypeAnn()->GetKind()) return TStatus::Ok; return AsTupleWrap(input, output, ctx); } TStatus ProcessUnknown(const TExprNode::TPtr& input, TExprContext&) { return UpdateAllChildLambdasConstraints(*input); } TStatus ValidateProviderCommitResult(const TExprNode::TPtr&, TExprContext&) { return TStatus::Ok; } TStatus ValidateProviderReadResult(const TExprNode::TPtr&, TExprContext&) { return TStatus::Ok; } TStatus ValidateProviderWriteResult(const TExprNode::TPtr&, TExprContext&) { return TStatus::Ok; } TStatus ValidateProviderConfigureResult(const TExprNode::TPtr&, TExprContext&) { return TStatus::Ok; } IGraphTransformer& GetTransformer(IDataProvider& provider) const { return provider.GetConstraintTransformer(InstantOnly, SubGraph); } private: template TStatus CopyAllFrom(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { Y_UNUSED(output); Y_UNUSED(ctx); input->CopyConstraints(*input->Child(Ndx)); return TStatus::Ok; } template TStatus FromFirst(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { Y_UNUSED(output); Y_UNUSED(ctx); TApplyConstraintFromInput<0, TConstraints...>::Do(input); return TStatus::Ok; } template TStatus FromSecond(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { Y_UNUSED(output); Y_UNUSED(ctx); TApplyConstraintFromInput<1, TConstraints...>::Do(input); return TStatus::Ok; } template TStatus CommonFromChildren(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { Y_UNUSED(output); TVector constraints; for (size_t i = StartFromChild; i < input->ChildrenSize(); ++i) { constraints.push_back(&input->Child(i)->GetConstraintSet()); } TApplyCommonConstraint::Do(input, constraints, ctx); return TStatus::Ok; } TStatus FromEmpty(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { input->AddConstraint(ctx.MakeConstraint()); return TStatus::Ok; } TStatus EmptyFromWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { auto set = input->Head().GetConstraintSet(); set.RemoveConstraint(TEmptyConstraintNode::Name()); if (!set) { const auto type = input->GetTypeAnn(); output = ctx.NewCallable(input->Pos(), GetEmptyCollectionName(type), {ExpandType(input->Pos(), *type, ctx)}); return TStatus::Repeat; } set.AddConstraint(ctx.MakeConstraint()); input->SetConstraints(set); return TStatus::Ok; } template TStatus FromFinalLambda(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { TStatus status = UpdateAllChildLambdasConstraints(*input); if (status != TStatus::Ok) { return status; } TApplyConstraintFromInput::Do(input); return FromFirst(input, output, ctx); } TStatus InheriteEmptyFromInput(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { auto status = UpdateAllChildLambdasConstraints(*input); if (status != TStatus::Ok) { return status; } return FromFirst(input, output, ctx); } template TStatus WideTopWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if constexpr (Sort) { TSortedConstraintNode::TContainerType sorted; sorted.reserve(input->Tail().ChildrenSize()); for (const auto& item : input->Tail().Children()) { if (item->Tail().IsCallable("Bool")) sorted.emplace_back(std::make_pair(TPartOfConstraintBase::TSetType{TPartOfConstraintBase::TPathType(1U, item->Head().Content())}, FromString(item->Tail().Tail().Content()))); else break; } if (!sorted.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(sorted))); } } return FromFirst(input, output, ctx); } TStatus SortWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (const auto status = UpdateLambdaConstraints(input->Tail()); status != TStatus::Ok) { return status; } if (const auto sorted = DeduceSortConstraint(*input->Child(1), *input->Child(2), ctx)) { input->AddConstraint(sorted->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); } return FromFirst(input, output, ctx); } TStatus AssumeConstraintsWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { TConstraintSet set; try { set = ctx.MakeConstraintSet(NYT::NodeFromYsonString(input->Tail().Content())); } catch (...) { ctx.AddError(TIssue(ctx.GetPosition(input->Pos()), TStringBuilder() << "Bad constraints yson-value: " << CurrentExceptionMessage())); return IGraphTransformer::TStatus::Error; } if (!set) { ctx.AddError(TIssue(ctx.GetPosition(input->Pos()), "AssumeConstraints with empty constraints set")); return IGraphTransformer::TStatus::Error; } for (auto constraint: set.GetAllConstraints()) { if (!constraint->IsApplicableToType(*input->GetTypeAnn())) { ctx.AddError(TIssue(ctx.GetPosition(input->Pos()), TStringBuilder() << *constraint << " is not applicable to " << *input->GetTypeAnn())); return IGraphTransformer::TStatus::Error; } } for (auto constr: input->Head().GetAllConstraints()) { if (!constr->GetName().starts_with("PartOf") && !set.GetConstraint(constr->GetName())) { set.AddConstraint(constr); } } input->SetConstraints(set); return IGraphTransformer::TStatus::Ok; } template TStatus AssumeUniqueWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { typename TUniqueConstraintNodeBase::TContentType content; for (auto i = 1U; i < input->ChildrenSize(); ++i) { TPartOfConstraintBase::TSetOfSetsType sets; sets.reserve(input->Child(i)->ChildrenSize()); for (const auto& list : input->Child(i)->Children()) { if (list->IsAtom()) sets.insert_unique(TPartOfConstraintBase::TSetType{TPartOfConstraintBase::TPathType(1U, list->Content())}); else if (list->IsList()) { TPartOfConstraintBase::TSetType columns; columns.reserve(list->ChildrenSize()); for (const auto& column: list->Children()) { if (column->IsAtom()) columns.insert_unique(TPartOfConstraintBase::TPathType(1U, column->Content())); else if (column->IsList()) { TPartOfConstraintBase::TPathType path(column->ChildrenSize()); std::transform(column->Children().cbegin(), column->Children().cend(), path.begin(), [](const TExprNode::TPtr& atom) { return atom->Content(); } ); columns.insert_unique(std::move(path)); } } sets.insert_unique(std::move(columns)); } } content.insert_unique(std::move(sets)); } if (content.empty()) content.insert_unique(TPartOfConstraintBase::TSetOfSetsType{TPartOfConstraintBase::TSetType{TPartOfConstraintBase::TPathType()}}); auto constraint = ctx.MakeConstraint>(std::move(content)); if (!constraint->IsApplicableToType(*input->GetTypeAnn())) { if constexpr (Strict) { ctx.AddError(TIssue(ctx.GetPosition(input->Pos()), TStringBuilder() << *constraint << " is not applicable to " << *input->GetTypeAnn())); } else { auto issue = TIssue(ctx.GetPosition(input->Pos()), TStringBuilder() << (Distinct ? "Distinct" : "Unique") << " sql hint contains invalid column: " << Endl << *constraint << " is not applicable to " << *input->GetTypeAnn()); SetIssueCode(EYqlIssueCode::TIssuesIds_EIssueCode_YQL_HINT_INVALID_PARAMETERS, issue); if (ctx.AddWarning(issue)) { output = input->HeadPtr(); return IGraphTransformer::TStatus::Repeat; } } return IGraphTransformer::TStatus::Error; } if constexpr (!Strict) { output = ctx.RenameNode(*input, Distinct ? "AssumeDistinct" : "AssumeUnique"); return IGraphTransformer::TStatus::Repeat; } if (const auto old = input->Head().GetConstraint>()) { if (old->Includes(*constraint)) { output = input->HeadPtr(); return TStatus::Repeat; } else constraint = TUniqueConstraintNodeBase::Merge(old, constraint, ctx); } input->AddConstraint(constraint); return FromFirst, TEmptyConstraintNode, TVarIndexConstraintNode>(input, output, ctx); } TStatus AssumeChoppedWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { TPartOfConstraintBase::TSetOfSetsType sets; for (auto i = 1U; i < input->ChildrenSize(); ++i) { TPartOfConstraintBase::TSetType columns; columns.reserve(input->Child(i)->ChildrenSize()); for (const auto& column: input->Child(i)->Children()) { if (column->IsAtom()) columns.insert_unique(TPartOfConstraintBase::TPathType(1U, column->Content())); else if (column->IsList()) { TPartOfConstraintBase::TPathType path(column->ChildrenSize()); std::transform(column->Children().cbegin(), column->Children().cend(), path.begin(), [](const TExprNode::TPtr& atom) { return atom->Content(); } ); columns.insert_unique(std::move(path)); } } sets.insert_unique(std::move(columns)); } const auto constraint = ctx.MakeConstraint(std::move(sets)); if (!constraint->IsApplicableToType(*input->GetTypeAnn())) { ctx.AddError(TIssue(ctx.GetPosition(input->Pos()), TStringBuilder() << *constraint << " is not applicable to " << *input->GetTypeAnn())); return IGraphTransformer::TStatus::Error; } if (const auto old = input->Head().GetConstraint()) { if (old->Equals(*constraint)) { output = input->HeadPtr(); return TStatus::Repeat; } } input->AddConstraint(constraint); return FromFirst(input, output, ctx); } template TStatus TopWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (const auto status = UpdateLambdaConstraints(input->Tail()); status != TStatus::Ok) { return status; } if constexpr (UseSort) { if (const auto sorted = DeduceSortConstraint(*input->Child(2), *input->Child(3), ctx)) { input->AddConstraint(sorted->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); } } return FromFirst(input, output, ctx); } template static void FilterFromHead(const TExprNode& input, TConstraintSet& constraints, const TPartOfConstraintBase::TPathFilter& filter, TExprContext& ctx) { if (const auto source = input.Head().GetConstraint()) { if (const auto filtered = source->FilterFields(ctx, filter)) { constraints.AddConstraint(filtered); } } } template static void ReduceFromHead(const TExprNode::TPtr& input, const TPartOfConstraintBase::TPathReduce& reduce, TExprContext& ctx) { if (const auto source = input->Head().GetConstraint()) { if (const auto filtered = source->RenameFields(ctx, reduce)) { if constexpr (Simplify) input->AddConstraint(filtered->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); else input->AddConstraint(filtered); } } } template static void FilterFromHead(const TExprNode::TPtr& input, const TPartOfConstraintBase::TPathFilter& filter, TExprContext& ctx) { if (const auto source = input->Head().GetConstraint()) { if (const auto filtered = source->FilterFields(ctx, filter)) { if constexpr (Simplify) input->AddConstraint(filtered->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); else input->AddConstraint(filtered); } } } template static void FilterFromHeadIfMissed(const TExprNode::TPtr& input, const TPartOfConstraintBase::TPathFilter& filter, TExprContext& ctx) { if (!input->GetConstraint()) FilterFromHead(input, filter, ctx); } TStatus SelectMembersWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { auto outItemType = input->GetTypeAnn(); while (outItemType->GetKind() == ETypeAnnotationKind::Optional) { outItemType = outItemType->Cast()->GetItemType(); } if (outItemType->GetKind() == ETypeAnnotationKind::Variant) { if (outItemType->Cast()->GetUnderlyingType()->GetKind() == ETypeAnnotationKind::Tuple) { const auto outSize = outItemType->Cast()->GetUnderlyingType()->Cast()->GetSize(); auto multi = input->Head().GetConstraint(); if (multi && multi->GetItems().back().first >= outSize) { TMultiConstraintNode::TMapType filteredItems; for (auto& item: multi->GetItems()) { if (item.first < outSize) { filteredItems.push_back(item); } } multi = filteredItems.empty() ? nullptr : ctx.MakeConstraint(std::move(filteredItems)); } if (multi) { input->AddConstraint(multi); } auto varIndex = input->Head().GetConstraint(); if (varIndex && varIndex->GetIndexMapping().back().first >= outSize) { TVarIndexConstraintNode::TMapType filteredItems; for (auto& item: varIndex->GetIndexMapping()) { if (item.first < outSize) { filteredItems.push_back(item); } } varIndex = filteredItems.empty() ? nullptr : ctx.MakeConstraint(std::move(filteredItems)); } if (varIndex) { input->AddConstraint(varIndex); } } } else if (outItemType->GetKind() == ETypeAnnotationKind::Struct) { const auto filter = [outItemType](const TPartOfConstraintBase::TPathType& path) { return !path.empty() && TPartOfConstraintBase::GetSubTypeByPath(path, *outItemType); }; FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); } return TStatus::Ok; } template TStatus CastWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const auto outItemType = input->GetTypeAnn(); const auto inItemType = input->Head().GetTypeAnn(); const auto filter = [inItemType, outItemType, toString = input->IsCallable({"ToString", "ToBytes"})](const TPartOfConstraintBase::TPathType& path) { if (const auto outType = TPartOfConstraintBase::GetSubTypeByPath(path, *outItemType)) { const auto inType = TPartOfConstraintBase::GetSubTypeByPath(path, *inItemType); return (toString && inType->GetKind() == ETypeAnnotationKind::Data && inType->Cast()->GetSlot() == EDataSlot::Utf8) || IsSameAnnotation(*outType, *inType); } return false; }; const auto filterForUnique = [inItemType, outItemType](const TPartOfConstraintBase::TPathType& path) { const auto castResult = CastResult(TPartOfConstraintBase::GetSubTypeByPath(path, *inItemType), TPartOfConstraintBase::GetSubTypeByPath(path, *outItemType)); return NUdf::ECastOptions::Complete == castResult || NUdf::ECastOptions::MayFail == castResult; }; const auto filterForDistinct = [inItemType, outItemType](const TPartOfConstraintBase::TPathType& path) { return NUdf::ECastOptions::Complete == CastResult(TPartOfConstraintBase::GetSubTypeByPath(path, *inItemType), TPartOfConstraintBase::GetSubTypeByPath(path, *outItemType)); }; FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filterForUnique, ctx); FilterFromHead(input, filterForDistinct, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filterForUnique, ctx); FilterFromHead(input, filterForDistinct, ctx); return TStatus::Ok; } TStatus DivePrefixMembersWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { const auto prefixes = input->Tail().Children(); const auto rename = [&prefixes](const TPartOfConstraintBase::TPathType& path) -> std::vector { if (path.empty()) return {}; for (const auto& p : prefixes) { if (const auto& prefix = p->Content(); path.front().starts_with(prefix)) { auto out = path; out.front() = out.front().substr(prefix.length()); return {std::move(out)}; } } return {}; }; ReduceFromHead(input, rename, ctx); ReduceFromHead(input, rename, ctx); ReduceFromHead(input, rename, ctx); ReduceFromHead(input, rename, ctx); return FromFirst(input, output, ctx); } TStatus ExtractMembersWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { const auto outItemType = GetSeqItemType(*input->GetTypeAnn()).Cast(); const auto filter = [outItemType](const TPartOfConstraintBase::TPathType& path) { return !path.empty() && outItemType->FindItem(path.front()); }; FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); return FromFirst(input, output, ctx); } TStatus RemovePrefixMembersWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { const TTypeAnnotationNode* outItemType = GetSeqItemType(input->GetTypeAnn()); if (!outItemType) { outItemType = input->GetTypeAnn(); } if (outItemType->GetKind() == ETypeAnnotationKind::Struct) { const auto outStructType = outItemType->Cast(); const auto filter = [outStructType](const TPartOfConstraintBase::TPathType& path) { return !path.empty() && outStructType->FindItem(path.front()); }; FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); } else if (outItemType->GetKind() == ETypeAnnotationKind::Variant) { if (auto multi = input->Head().GetConstraint()) { TMultiConstraintNode::TMapType multiItems; auto tupleUnderType = outItemType->Cast()->GetUnderlyingType()->Cast(); for (auto& item: multi->GetItems()) { YQL_ENSURE(item.first < tupleUnderType->GetSize()); auto& constr = multiItems[item.first]; const auto outStructType = tupleUnderType->GetItems()[item.first]->Cast(); const auto filter = [outStructType](const TPartOfConstraintBase::TPathType& path) { return !path.empty() && outStructType->FindItem(path.front()); }; FilterFromHead(*input, constr, filter, ctx); FilterFromHead(*input, constr, filter, ctx); FilterFromHead(*input, constr, filter, ctx); FilterFromHead(*input, constr, filter, ctx); } input->AddConstraint(ctx.MakeConstraint(std::move(multiItems))); } } return FromFirst(input, output, ctx); } // TODO: Empty for false condition template TStatus FilterWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (const auto status = UpdateLambdaConstraints(*input->Child(1)); status != TStatus::Ok) { return status; } if constexpr (Ordered) { FromFirst(input, output, ctx); } return FromFirst(input, output, ctx); } template static const TConstraint* GetConstraintFromWideResultLambda(const TExprNode& lambda, TExprContext& ctx); template static const TConstraintType* GetLambdaConstraint(const TExprNode& lambda, TExprContext& ctx) { if (2U == lambda.ChildrenSize()) return lambda.Tail().GetConstraint(); TVector constraints; constraints.reserve(lambda.ChildrenSize() - 1U); for (size_t i = 1U; i < lambda.ChildrenSize(); ++i) { constraints.emplace_back(&lambda.Child(i)->GetConstraintSet()); } return TConstraintType::MakeCommon(constraints, ctx); } template static const TConstraintType* GetConstraintFromLambda(const TExprNode& lambda, TExprContext& ctx) { if constexpr (WideLambda) return GetConstraintFromWideResultLambda(lambda, ctx); else return GetLambdaConstraint(lambda, ctx); } static std::optional GetDirection(const TExprNode& dir) { if (dir.IsCallable("Bool")) return IsTrue(dir.Tail().Content()); if (dir.IsCallable("Not")) if (const auto d = GetDirection(dir.Head())) return !*d; return std::nullopt; } static std::vector> ExtractSimpleSortTraits(const TExprNode& sortDirections, const TExprNode& keySelectorLambda) { const auto& keySelectorBody = keySelectorLambda.Tail(); const auto& keySelectorArg = keySelectorLambda.Head().Head(); std::vector> columns; if (const auto dir = GetDirection(sortDirections)) columns.emplace_back(TPartOfConstraintBase::TPathType(), *dir); else if (sortDirections.IsList()) if (const auto size = keySelectorBody.ChildrenSize()) { columns.reserve(size); for (auto i = 0U; i < size; ++i) if (const auto dir = GetDirection(*sortDirections.Child(i))) columns.emplace_back(TPartOfConstraintBase::TPathType(), *dir); else return {}; } else return {}; else return {}; if (keySelectorBody.IsList()) if (const auto size = keySelectorBody.ChildrenSize()) { TPartOfConstraintBase::TSetType set; set.reserve(size); columns.resize(size, std::make_pair(TPartOfConstraintBase::TPathType(), columns.back().second)); auto it = columns.begin(); for (auto i = 0U; i < size; ++i) { if (auto path = GetPathToKey(*keySelectorBody.Child(i), keySelectorArg)) { if (set.insert(*path).second) it++->first = std::move(*path); else if (columns.cend() != it) it = columns.erase(it); } else { return {}; } } } else return {}; else if (auto path = GetPathToKey(keySelectorBody, keySelectorArg)) if (columns.size() == 1U) columns.front().first = std::move(*path); else return {}; else return {}; return columns; } template static void GetFromMapLambda(const TInput& input, const TConstraintSet& handler, TConstraintSet& output, bool isSingleItem, TExprContext& ctx) { constexpr bool isOrderConstraint = std::is_same() || std::is_same(); if (const auto lambda = handler.GetConstraint()) { const auto original = input.template GetConstraint(); if constexpr (OrderedMap || !isOrderConstraint) { if (original) { if (const auto complete = TConstraint::MakeComplete(ctx, lambda->GetColumnMapping(), original)) { output.AddConstraint(complete); } } } if (const auto part = input.template GetConstraint()) { auto mapping = lambda->GetColumnMapping(); for (auto it = mapping.cbegin(); mapping.cend() != it;) { if (part->GetColumnMapping().contains(it->first)) ++it; else it = mapping.erase(it); } if (!mapping.empty()) { output.AddConstraint(ctx.MakeConstraint(std::move(mapping))); } } else if (isOrderConstraint || isSingleItem) { if (const auto filtered = lambda->RemoveOriginal(ctx, original)) output.AddConstraint(filtered); } } } template static void GetFromMapLambda(const TExprNode::TPtr& input, bool isSingleItem, TExprContext& ctx) { constexpr bool isOrderConstraint = std::is_same() || std::is_same(); if (const auto lambda = GetConstraintFromLambda(input->Tail(), ctx)) { const auto original = GetDetailed(input->Head().GetConstraint(), *input->Head().GetTypeAnn(), ctx); if constexpr (OrderedMap || !isOrderConstraint) { if (original) { if (const auto complete = TConstraint::MakeComplete(ctx, lambda->GetColumnMapping(), original)) { input->AddConstraint(complete->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); } } } if (const auto part = input->Head().GetConstraint()) { auto mapping = lambda->GetColumnMapping(); for (auto it = mapping.cbegin(); mapping.cend() != it;) { if (part->GetColumnMapping().contains(it->first)) ++it; else it = mapping.erase(it); } if (!mapping.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(mapping))); } } else if (isOrderConstraint || isSingleItem) { if (const auto filtered = lambda->RemoveOriginal(ctx, original)) input->AddConstraint(filtered); } } } template TStatus MapWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { auto argConstraints = GetConstraintsForInputArgument(*input, ctx); if constexpr (Ordered && !(Flat || WideInput || WideOutput)) { // TODO: is temporary crutch for MapNext. if (argConstraints.size() < input->Tail().Head().ChildrenSize()) argConstraints.resize(input->Tail().Head().ChildrenSize(), argConstraints.front()); } if (const auto status = UpdateLambdaConstraints(input->TailRef(), ctx, argConstraints); status != TStatus::Ok) { return status; } const bool singleItem = ETypeAnnotationKind::Optional == input->GetTypeAnn()->GetKind(); GetFromMapLambda(input, singleItem, ctx); GetFromMapLambda(input, singleItem, ctx); GetFromMapLambda(input, singleItem, ctx); GetFromMapLambda(input, singleItem, ctx); const auto lambdaVarIndex = GetConstraintFromLambda(input->Tail(), ctx); const auto lambdaMulti = GetConstraintFromLambda(input->Tail(), ctx); const auto inItemType = GetSeqItemType(input->Head().GetTypeAnn()); const bool multiInput = ETypeAnnotationKind::Variant == inItemType->GetKind(); if (const auto varIndex = input->Head().GetConstraint()) { if (multiInput) { if (lambdaVarIndex) { if (const auto outVarIndex = GetVarIndexOverVarIndexConstraint(*varIndex, *lambdaVarIndex, ctx)) { input->AddConstraint(outVarIndex); } } } else { if (lambdaMulti) { TVarIndexConstraintNode::TMapType remapItems; for (auto& multiItem: lambdaMulti->GetItems()) { for (auto& varItem: varIndex->GetIndexMapping()) { remapItems.push_back(std::make_pair(multiItem.first, varItem.second)); } } if (!remapItems.empty()) { ::SortUnique(remapItems); input->AddConstraint(ctx.MakeConstraint(std::move(remapItems))); } } else { input->AddConstraint(varIndex); } } } const auto inputMulti = input->Head().GetConstraint(); if (lambdaMulti && !input->Head().GetConstraint()) { TMultiConstraintNode::TMapType remappedItems; for (auto& item: lambdaMulti->GetItems()) { remappedItems.push_back(std::make_pair(item.first, TConstraintSet{})); if (!multiInput) { // remapping one to many GetFromMapLambda(input->Head(), item.second, remappedItems.back().second, singleItem, ctx); GetFromMapLambda(input->Head(), item.second, remappedItems.back().second, singleItem, ctx); GetFromMapLambda(input->Head(), item.second, remappedItems.back().second, singleItem, ctx); GetFromMapLambda(input->Head(), item.second, remappedItems.back().second, singleItem, ctx); if (const auto empty = item.second.template GetConstraint()) { remappedItems.pop_back(); } } else if (lambdaVarIndex && inputMulti) { const auto range = lambdaVarIndex->GetIndexMapping().equal_range(item.first); switch (std::distance(range.first, range.second)) { case 0: // new index break; case 1: // remapping 1 to 1 if (const auto origConstr = inputMulti->GetItem(range.first->second)) { GetFromMapLambda(*origConstr, item.second, remappedItems.back().second, singleItem, ctx); GetFromMapLambda(*origConstr, item.second, remappedItems.back().second, singleItem, ctx); GetFromMapLambda(*origConstr, item.second, remappedItems.back().second, singleItem, ctx); GetFromMapLambda(*origConstr, item.second, remappedItems.back().second, singleItem, ctx); if (const auto empty = item.second.template GetConstraint()) { remappedItems.pop_back(); } } else { remappedItems.pop_back(); } break; default: // remapping many to one { std::vector nonEmpty; for (auto i = range.first; i != range.second; ++i) { if (auto origConstr = inputMulti->GetItem(i->second)) { nonEmpty.push_back(origConstr); } } EraseIf(nonEmpty, [] (const TConstraintSet* c) { return !!c->GetConstraint(); }); if (nonEmpty.empty()) { remappedItems.back().second.AddConstraint(ctx.MakeConstraint()); } else if (nonEmpty.size() == 1) { remappedItems.back().second = std::move(*nonEmpty.front()); } } } } else { remappedItems.back().second = item.second; } } if (remappedItems) { input->AddConstraint(ctx.MakeConstraint(std::move(remappedItems))); } } else if (inputMulti && lambdaVarIndex) { // Many to one const auto range = lambdaVarIndex->GetIndexMapping().equal_range(0); std::vector nonEmpty; for (auto i = range.first; i != range.second; ++i) { if (auto origConstr = inputMulti->GetItem(i->second)) { nonEmpty.push_back(origConstr); } } EraseIf(nonEmpty, [] (const TConstraintSet* c) { return !!c->GetConstraint(); }); if (nonEmpty.empty()) { input->AddConstraint(ctx.MakeConstraint()); } else if (nonEmpty.size() == 1) { input->SetConstraints(*nonEmpty.front()); } } if constexpr (Flat) { if (const auto lambdaEmpty = GetConstraintFromLambda(input->Tail(), ctx)) { input->AddConstraint(lambdaEmpty); const auto& filter = std::bind(&TPartOfConstraintBase::GetSubTypeByPath, std::placeholders::_1, std::cref(GetSeqItemType(*input->GetTypeAnn()))); FilterFromHeadIfMissed(input, filter, ctx); FilterFromHeadIfMissed(input, filter, ctx); if constexpr (Ordered) { FilterFromHeadIfMissed(input, filter, ctx); FilterFromHeadIfMissed(input, filter, ctx); } } } return FromFirst(input, output, ctx); } template TStatus LMapWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { TConstraintNode::TListType argConstraints; for (const auto c: input->Head().GetAllConstraints()) { if (Ordered || (c->GetName() != TSortedConstraintNode::Name() && c->GetName() != TChoppedConstraintNode::Name())) { argConstraints.push_back(c); } } if (const auto status = UpdateLambdaConstraints(input->TailRef(), ctx, {argConstraints}); status != TStatus::Ok) { return status; } TSet except; if constexpr (!Ordered) { except.insert(TSortedConstraintNode::Name()); except.insert(TChoppedConstraintNode::Name()); } if (input->Tail().GetTypeAnn()->GetKind() == ETypeAnnotationKind::Optional) { except.insert(TEmptyConstraintNode::Name()); } CopyExcept(*input, input->Tail(), except); return FromFirst(input, output, ctx); } TStatus AsListWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (input->ChildrenSize() == 1) { if (const auto unique = input->Head().GetConstraint()) { input->AddConstraint(unique); } if (const auto unique = input->Head().GetConstraint()) { input->AddConstraint(unique); } if (const auto part = input->Head().GetConstraint()) { input->AddConstraint(part); } if (const auto part = input->Head().GetConstraint()) { input->AddConstraint(part); } } return CommonFromChildren<0, TPartOfSortedConstraintNode, TPartOfChoppedConstraintNode, TVarIndexConstraintNode, TMultiConstraintNode>(input, output, ctx); } template TStatus ExtendWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (input->ChildrenSize() == 1) { if (const auto unique = input->Head().GetConstraint()) { input->AddConstraint(unique); } if (const auto part = input->Head().GetConstraint()) { input->AddConstraint(part); } if (const auto unique = input->Head().GetConstraint()) { input->AddConstraint(unique); } if (const auto part = input->Head().GetConstraint()) { input->AddConstraint(part); } if constexpr (Ordered) { if (const auto sorted = input->Head().GetConstraint()) { input->AddConstraint(sorted); } if (const auto part = input->Head().GetConstraint()) { input->AddConstraint(part); } if (const auto sorted = input->Head().GetConstraint()) { input->AddConstraint(sorted); } if (const auto part = input->Head().GetConstraint()) { input->AddConstraint(part); } } } return CommonFromChildren<0, TEmptyConstraintNode, TVarIndexConstraintNode, TMultiConstraintNode>(input, output, ctx); } template TStatus MergeWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (auto sort = MakeCommonConstraint(input, 0, ctx)) { if (Union && input->ChildrenSize() > 1) { // Check and exclude modified keys from final constraint const auto resultItemType = input->GetTypeAnn()->Cast()->GetItemType(); std::vector inputs; for (const auto& child: input->Children()) { inputs.emplace_back(child->GetTypeAnn()->Cast()->GetItemType()); } auto content = sort->GetContent(); for (auto i = 0U; i < content.size(); ++i) { for (auto it = content[i].first.cbegin(); content[i].first.cend() != it;) { const auto resultItemSubType = TPartOfConstraintBase::GetSubTypeByPath(*it, *resultItemType); YQL_ENSURE(resultItemSubType, "Missing " << *it << " in result type"); auto childNdx = 0U; while (childNdx < input->ChildrenSize()) { if (const auto inputItemSubType = TPartOfConstraintBase::GetSubTypeByPath(*it, *inputs[childNdx])) { if (IsSameAnnotation(*inputItemSubType, *resultItemSubType)) { ++childNdx; continue; } } else { YQL_ENSURE(input->Child(childNdx)->GetConstraint(), "Missing column " << *it << " in non empty input type"); } break; } if (childNdx < input->ChildrenSize()) it = content[i].first.erase(it); else ++it; } if (content[i].first.empty()) { content.resize(i); break; } } sort = content.empty() ? nullptr : ctx.MakeConstraint(std::move(content)); } if (sort) { input->AddConstraint(sort); } } return ExtendWrap(input, output, ctx); } TStatus TakeWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (input->Tail().IsCallable("Uint64") && !FromString(input->Tail().Head().Content())) { input->AddConstraint(ctx.MakeConstraint()); } return CopyAllFrom<0>(input, output, ctx); } TStatus MemberWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const auto& memberName = input->Tail().Content(); const auto& structNode = input->Head(); if (const auto emptyConstraint = structNode.GetConstraint()) { input->AddConstraint(emptyConstraint); } else { if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } } if (structNode.IsCallable("AsStruct")) { for (const auto& child: structNode.Children()) { if (child->Head().IsAtom(memberName)) { TApplyConstraintFromInput<1, TVarIndexConstraintNode>::Do(child); break; } } } else { TApplyConstraintFromInput<0, TVarIndexConstraintNode>::Do(input); } return TStatus::Ok; } TStatus AsTupleWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { TPartOfSortedConstraintNode::TMapType sorted; TPartOfChoppedConstraintNode::TMapType chopped; TPartOfUniqueConstraintNode::TMapType uniques; TPartOfDistinctConstraintNode::TMapType distincts; std::vector structConstraints; for (auto i = 0U; i < input->ChildrenSize(); ++i) { const auto child = input->Child(i); const auto& name = ctx.GetIndexAsString(i); if (const auto part = child->GetConstraint()) { TPartOfSortedConstraintNode::UniqueMerge(sorted, part->GetColumnMapping(name)); } if (const auto part = child->GetConstraint()) { TPartOfChoppedConstraintNode::UniqueMerge(chopped, part->GetColumnMapping(name)); } if (const auto part = child->GetConstraint()) { TPartOfUniqueConstraintNode::UniqueMerge(uniques, part->GetColumnMapping(name)); } if (const auto part = child->GetConstraint()) { TPartOfDistinctConstraintNode::UniqueMerge(distincts, part->GetColumnMapping(name)); } if (const auto& valueNode = SkipModifiers(child); TCoMember::Match(valueNode) || TCoNth::Match(valueNode)) { structConstraints.push_back(&valueNode->Head().GetConstraintSet()); } else if (valueNode->IsArgument() && ETypeAnnotationKind::Struct != valueNode->GetTypeAnn()->GetKind()) { structConstraints.push_back(&valueNode->GetConstraintSet()); } } if (!sorted.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(sorted))); } if (!chopped.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(chopped))); } if (!uniques.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(uniques))); } if (!distincts.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(distincts))); } if (const auto varIndex = TVarIndexConstraintNode::MakeCommon(structConstraints, ctx)) { input->AddConstraint(varIndex); } return TStatus::Ok; } TStatus AsStructWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { TPartOfSortedConstraintNode::TMapType sorted; TPartOfChoppedConstraintNode::TMapType chopped; TPartOfUniqueConstraintNode::TMapType uniques; TPartOfDistinctConstraintNode::TMapType distincts; std::vector structConstraints; for (const auto& child : input->Children()) { const auto& name = child->Head().Content(); if (const auto part = child->Tail().GetConstraint()) { TPartOfSortedConstraintNode::UniqueMerge(sorted, part->GetColumnMapping(name)); } if (const auto part = child->Tail().GetConstraint()) { TPartOfChoppedConstraintNode::UniqueMerge(chopped, part->GetColumnMapping(name)); } if (const auto part = child->Tail().GetConstraint()) { TPartOfUniqueConstraintNode::UniqueMerge(uniques, part->GetColumnMapping(name)); } if (const auto part = child->Tail().GetConstraint()) { TPartOfDistinctConstraintNode::UniqueMerge(distincts, part->GetColumnMapping(name)); } if (const auto valueNode = SkipModifiers(&child->Tail()); TCoMember::Match(valueNode) || TCoNth::Match(valueNode)) { structConstraints.push_back(&valueNode->Head().GetConstraintSet()); } else if (valueNode->Type() == TExprNode::Argument) { structConstraints.push_back(&valueNode->GetConstraintSet()); } } if (!sorted.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(sorted))); } if (!chopped.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(chopped))); } if (!uniques.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(uniques))); } if (!distincts.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(distincts))); } if (const auto varIndex = TVarIndexConstraintNode::MakeCommon(structConstraints, ctx)) { input->AddConstraint(varIndex); } return TStatus::Ok; } TStatus FlattenMembersWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { TPartOfSortedConstraintNode::TMapType sorted; TPartOfChoppedConstraintNode::TMapType chopped; TPartOfUniqueConstraintNode::TMapType uniques; TPartOfDistinctConstraintNode::TMapType distincts; for (const auto& child : input->Children()) { const auto& prefix = child->Head().Content(); if (const auto part = child->Tail().GetConstraint()) { TPartOfSortedConstraintNode::UniqueMerge(sorted, part->GetColumnMapping(ctx, prefix)); } if (const auto part = child->Tail().GetConstraint()) { TPartOfChoppedConstraintNode::UniqueMerge(chopped, part->GetColumnMapping(ctx, prefix)); } if (const auto part = child->Tail().GetConstraint()) { TPartOfUniqueConstraintNode::UniqueMerge(uniques, part->GetColumnMapping(ctx, prefix)); } if (const auto part = child->Tail().GetConstraint()) { TPartOfDistinctConstraintNode::UniqueMerge(distincts, part->GetColumnMapping(ctx, prefix)); } } if (!sorted.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(sorted))); } if (!chopped.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(chopped))); } if (!uniques.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(uniques))); } if (!distincts.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(distincts))); } return TStatus::Ok; } template static void AddPartOf(const TExprNode::TPtr& input, TExprContext& ctx) { typename TPartOfConstraint::TMapType map; if (const auto part = input->Head().GetConstraint()) { map = part->GetColumnMapping(); } if (const auto part = input->Tail().GetConstraint()) { TPartOfConstraint::UniqueMerge(map, part->GetColumnMapping(input->Child(1)->Content())); } if (!map.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(map))); } } template static void ReplacePartOf(const TExprNode::TPtr& input, TExprContext& ctx) { typename TPartOfConstraint::TMapType sorted; const auto& name = input->Child(1)->Content(); if (const auto part = input->Head().GetConstraint()) { if (const auto filtered = part->FilterFields(ctx, [&name](const TPartOfConstraintBase::TPathType& path) { return !path.empty() && path.front() != name; })) { sorted = filtered->GetColumnMapping(); } } if (const auto part = input->Tail().GetConstraint()) { TPartOfConstraint::UniqueMerge(sorted, part->GetColumnMapping(name)); } if (!sorted.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(sorted))); } } TStatus AddMemberWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const auto& addStructNode = input->Head(); const auto& extraFieldNode = input->Tail(); if (const auto emptyConstraint = addStructNode.GetConstraint()) { input->AddConstraint(emptyConstraint); } AddPartOf(input, ctx); AddPartOf(input, ctx); AddPartOf(input, ctx); AddPartOf(input, ctx); TVector structConstraints; structConstraints.push_back(&addStructNode.GetConstraintSet()); if (const auto& valueNode = SkipModifiers(&extraFieldNode); TCoMember::Match(valueNode) || TCoNth::Match(valueNode)) { structConstraints.push_back(&valueNode->Head().GetConstraintSet()); } else if (valueNode->Type() == TExprNode::Argument) { structConstraints.push_back(&valueNode->GetConstraintSet()); } if (const auto varIndex = TVarIndexConstraintNode::MakeCommon(structConstraints, ctx)) { input->AddConstraint(varIndex); } return TStatus::Ok; } TStatus RemoveMemberWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { const auto& name = input->Tail().Content(); const auto filter = [&name](const TPartOfConstraintBase::TPathType& path) { return !path.empty() && path.front() != name; }; FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); FilterFromHead(input, filter, ctx); return FromFirst(input, output, ctx); } TStatus ReplaceMemberWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { TVector structConstraints; structConstraints.push_back(&input->Head().GetConstraintSet()); ReplacePartOf(input, ctx); ReplacePartOf(input, ctx); ReplacePartOf(input, ctx); ReplacePartOf(input, ctx); if (const auto varIndex = TVarIndexConstraintNode::MakeCommon(structConstraints, ctx)) { input->AddConstraint(varIndex); } return TStatus::Ok; } TStatus ListWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { switch (input->ChildrenSize()) { case 1: return FromEmpty(input, output, ctx); case 2: return FromSecond(input, output, ctx); default: break; } return CommonFromChildren<1, TVarIndexConstraintNode, TMultiConstraintNode>(input, output, ctx); } template static TSmallVec GetConstraintsForInputArgument(const TExprNode& node, TExprContext& ctx) { TSmallVec argsConstraints(WideInput ? node.Child(1U)->Head().ChildrenSize() : 1U); if constexpr (WideInput) { if constexpr (Ordered) { if (const auto& mapping = TPartOfSortedConstraintNode::GetCommonMapping(node.Head().GetConstraint(), node.Head().GetConstraint()); !mapping.empty()) { for (ui32 i = 0U; i < argsConstraints.size(); ++i) { if (auto extracted = TPartOfSortedConstraintNode::ExtractField(mapping, ctx.GetIndexAsString(i)); !extracted.empty()) { argsConstraints[i].emplace_back(ctx.MakeConstraint(std::move(extracted))); } } } if (const auto& mapping = TPartOfChoppedConstraintNode::GetCommonMapping(node.Head().GetConstraint(), node.Head().GetConstraint()); !mapping.empty()) { for (ui32 i = 0U; i < argsConstraints.size(); ++i) { if (auto extracted = TPartOfChoppedConstraintNode::ExtractField(mapping, ctx.GetIndexAsString(i)); !extracted.empty()) { argsConstraints[i].emplace_back(ctx.MakeConstraint(std::move(extracted))); } } } } if constexpr (WithUnique) { if (const auto& mapping = TPartOfUniqueConstraintNode::GetCommonMapping(node.Head().GetConstraint(), node.Head().GetConstraint()); !mapping.empty()) { for (ui32 i = 0U; i < argsConstraints.size(); ++i) { if (auto extracted = TPartOfUniqueConstraintNode::ExtractField(mapping, ctx.GetIndexAsString(i)); !extracted.empty()) { argsConstraints[i].emplace_back(ctx.MakeConstraint(std::move(extracted))); } } } if (const auto& mapping = TPartOfDistinctConstraintNode::GetCommonMapping(node.Head().GetConstraint(), node.Head().GetConstraint()); !mapping.empty()) { for (ui32 i = 0U; i < argsConstraints.size(); ++i) { if (auto extracted = TPartOfDistinctConstraintNode::ExtractField(mapping, ctx.GetIndexAsString(i)); !extracted.empty()) { argsConstraints[i].emplace_back(ctx.MakeConstraint(std::move(extracted))); } } } } } else { if (const auto inItemType = GetSeqItemType(node.Head().GetTypeAnn())) { if (inItemType->GetKind() == ETypeAnnotationKind::Variant) { if (inItemType->Cast()->GetUnderlyingType()->GetKind() == ETypeAnnotationKind::Tuple) { const auto tupleType = inItemType->Cast()->GetUnderlyingType()->Cast(); argsConstraints.front().push_back(ctx.MakeConstraint(*inItemType->Cast())); TMultiConstraintNode::TMapType multiItems; multiItems.reserve(tupleType->GetSize()); for (size_t i = 0; i < tupleType->GetSize(); ++i) { multiItems.emplace_back(i, TConstraintSet{}); if constexpr (WithUnique) { const auto inputMulti = node.Head().GetConstraint(); if (const auto inputConstr = inputMulti ? inputMulti->GetItem(i) : nullptr) { if (auto mapping = TPartOfUniqueConstraintNode::GetCommonMapping(inputConstr->GetConstraint(), inputConstr->GetConstraint()); !mapping.empty()) { multiItems.back().second.AddConstraint(ctx.MakeConstraint(std::move(mapping))); } if (auto mapping = TPartOfDistinctConstraintNode::GetCommonMapping(inputConstr->GetConstraint(), inputConstr->GetConstraint()); !mapping.empty()) { multiItems.back().second.AddConstraint(ctx.MakeConstraint(std::move(mapping))); } } } } if (!multiItems.empty()) { argsConstraints.front().emplace_back(ctx.MakeConstraint(std::move(multiItems))); } } } else { if constexpr (Ordered) { if (auto mapping = TPartOfSortedConstraintNode::GetCommonMapping(GetDetailed(node.Head().GetConstraint(), *node.Head().GetTypeAnn(), ctx), node.Head().GetConstraint()); !mapping.empty()) { argsConstraints.front().emplace_back(ctx.MakeConstraint(std::move(mapping))); } if (auto mapping = TPartOfChoppedConstraintNode::GetCommonMapping(GetDetailed(node.Head().GetConstraint(), *node.Head().GetTypeAnn(), ctx), node.Head().GetConstraint()); !mapping.empty()) { argsConstraints.front().emplace_back(ctx.MakeConstraint(std::move(mapping))); } } if constexpr (WithUnique) { if (auto mapping = TPartOfUniqueConstraintNode::GetCommonMapping(GetDetailed(node.Head().GetConstraint(), *node.Head().GetTypeAnn(), ctx), node.Head().GetConstraint()); !mapping.empty()) { argsConstraints.front().emplace_back(ctx.MakeConstraint(std::move(mapping))); } if (auto mapping = TPartOfDistinctConstraintNode::GetCommonMapping(GetDetailed(node.Head().GetConstraint(), *node.Head().GetTypeAnn(), ctx), node.Head().GetConstraint()); !mapping.empty()) { argsConstraints.front().emplace_back(ctx.MakeConstraint(std::move(mapping))); } } } } } return argsConstraints; } TStatus DictWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { const std::vector k(1U, ctx.GetIndexAsString(0U)); input->AddConstraint(ctx.MakeConstraint(k)); input->AddConstraint(ctx.MakeConstraint(k)); if (input->ChildrenSize() == 1) { return FromEmpty(input, output, ctx); } return TStatus::Ok; } TStatus DictFromKeysWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { if (input->Child(1)->ChildrenSize() == 0) { input->AddConstraint(ctx.MakeConstraint()); } const std::vector k(1U, ctx.GetIndexAsString(0U)); input->AddConstraint(ctx.MakeConstraint(k)); input->AddConstraint(ctx.MakeConstraint(k)); return TStatus::Ok; } template TStatus PassOrEmptyWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (const auto part = input->Tail().GetConstraint()) if (const auto filtered = part->CompleteOnly(ctx)) input->AddConstraint(filtered); if (const auto part = input->Tail().GetConstraint()) if (const auto filtered = part->CompleteOnly(ctx)) input->AddConstraint(filtered); if (const auto part = input->Tail().GetConstraint()) if (const auto filtered = part->CompleteOnly(ctx)) input->AddConstraint(filtered); if (const auto part = input->Tail().GetConstraint()) if constexpr (IsList) { if (const auto filtered = part->CompleteOnly(ctx)) input->AddConstraint(filtered); } else input->AddConstraint(part); if constexpr (IsFlat) { if (const auto empty = input->Tail().GetConstraint()) input->AddConstraint(empty); } return FromSecond(input, output, ctx); } TStatus IfWrap(const TExprNode::TPtr& input, TExprNode::TPtr&, TExprContext& ctx) const { std::vector constraints; constraints.reserve((input->ChildrenSize() << 1U) + 1U); constraints.emplace_back(&input->Tail().GetConstraintSet()); for (auto i = 0U; i < input->ChildrenSize() - 1U; ++i) { constraints.emplace_back(&input->Child(++i)->GetConstraintSet()); } if (constraints.empty()) input->AddConstraint(ctx.MakeConstraint()); else if (1U == constraints.size()) input->SetConstraints(**constraints.cbegin()); else TApplyCommonConstraint::Do(input, constraints, ctx); return TStatus::Ok; } TStatus IfPresentWrap(const TExprNode::TPtr& input, TExprNode::TPtr&, TExprContext& ctx) const { auto optionals = input->ChildrenList(); const auto lambdaIndex = optionals.size() - 2U; auto lambda = std::move(optionals[lambdaIndex]); optionals.resize(lambdaIndex); std::vector constraints; constraints.reserve(optionals.size()); std::transform(optionals.cbegin(), optionals.cend(), std::back_inserter(constraints), [](const TExprNode::TPtr& node){ return node->GetAllConstraints(); }); if (const auto status = UpdateLambdaConstraints(input->ChildRef(lambdaIndex), ctx, constraints); status != TStatus::Ok) { return status; } if (std::any_of(optionals.cbegin(), optionals.cend(), [] (const TExprNode::TPtr& node) { return bool(node->GetConstraint()); })) { input->CopyConstraints(input->Tail()); return TStatus::Ok; } const std::vector both = { &lambda->GetConstraintSet(), &input->Tail().GetConstraintSet() }; TApplyCommonConstraint::Do(input, both, ctx); return TStatus::Ok; } TStatus ReverseWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (const auto sorted = input->Head().GetConstraint()) { auto content = sorted->GetContent(); std::for_each(content.begin(), content.end(), [](std::pair& pair) { pair.second = !pair.second; }); input->AddConstraint(ctx.MakeConstraint(std::move(content))); } return FromFirst(input, output, ctx); } TStatus SwitchWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { TStatus status = TStatus::Ok; TDynBitMap outFromChildren; // children, from which take a multi constraint for output if (const auto multi = input->Head().GetConstraint()) { for (size_t i = 2; i < input->ChildrenSize(); ++i) { TMultiConstraintNode::TMapType items; ui32 lambdaInputIndex = 0; for (auto& child : input->Child(i)->Children()) { const ui32 index = FromString(child->Content()); if (auto c = multi->GetItem(index)) { items[lambdaInputIndex] = *c; outFromChildren.Set(i + 1); } ++lambdaInputIndex; } TConstraintNode::TListType argConstraints; if (!items.empty()) { if (input->Child(i)->ChildrenSize() > 1) { argConstraints.push_back(ctx.MakeConstraint(std::move(items))); argConstraints.push_back(ctx.MakeConstraint(input->Child(i)->ChildrenSize())); } else { argConstraints = items.front().second.GetAllConstraints(); } } status = status.Combine(UpdateLambdaConstraints(input->ChildRef(++i), ctx, {argConstraints})); } } else { const bool inVar = GetSeqItemType(*input->Head().GetTypeAnn()).GetKind() == ETypeAnnotationKind::Variant; const TSmallVec argConstraints(1U, inVar ? TConstraintNode::TListType() : input->Head().GetAllConstraints()); for (size_t i = 3; i < input->ChildrenSize(); i += 2) { status = status.Combine(UpdateLambdaConstraints(input->ChildRef(i), ctx, argConstraints)); } outFromChildren.Set(0, input->ChildrenSize()); } if (status != TStatus::Ok) { return status; } const auto inputVarIndex = input->Head().GetConstraint(); const bool emptyInput = input->Head().GetConstraint(); if (GetSeqItemType(*input->GetTypeAnn()).GetKind() == ETypeAnnotationKind::Variant) { ui32 outIndexOffset = 0; TMultiConstraintNode::TMapType multiItems; TVarIndexConstraintNode::TMapType remapItems; bool emptyOut = true; for (size_t i = 2; i < input->ChildrenSize(); i += 2) { const auto lambda = input->Child(i + 1); const auto& lambdaItemType = GetSeqItemType(*lambda->GetTypeAnn()); if (inputVarIndex) { if (auto varIndex = lambda->GetConstraint()) { for (auto& item: varIndex->GetIndexMapping()) { YQL_ENSURE(item.second < input->Child(i)->ChildrenSize()); const auto srcIndex = FromString(input->Child(i)->Child(item.second)->Content()); remapItems.push_back(std::make_pair(outIndexOffset + item.first, srcIndex)); } } else if (lambdaItemType.GetKind() == ETypeAnnotationKind::Variant && input->Child(i)->ChildrenSize() == 1) { const auto srcIndex = FromString(input->Child(i)->Head().Content()); for (size_t j = 0; j < lambdaItemType.Cast()->GetUnderlyingType()->Cast()->GetSize(); ++j) { remapItems.push_back(std::make_pair(outIndexOffset + j, srcIndex)); } } else if (lambdaItemType.GetKind() != ETypeAnnotationKind::Variant && input->Child(i)->ChildrenSize() > 1) { for (auto& child : input->Child(i)->Children()) { const auto srcIndex = FromString(child->Content()); remapItems.push_back(std::make_pair(outIndexOffset, srcIndex)); } } } const bool lambdaEmpty = lambda->GetConstraint(); if (!lambdaEmpty) { emptyOut = false; } if (lambdaItemType.GetKind() == ETypeAnnotationKind::Variant) { if (!emptyInput && outFromChildren.Test(i + 1)) { if (auto multi = lambda->GetConstraint()) { for (auto& item: multi->GetItems()) { multiItems.insert_unique(std::make_pair(outIndexOffset + item.first, item.second)); } } } outIndexOffset += lambdaItemType.Cast()->GetUnderlyingType()->Cast()->GetSize(); } else { if (!emptyInput && outFromChildren.Test(i + 1) && !lambdaEmpty) { multiItems[outIndexOffset] = lambda->GetConstraintSet(); } ++outIndexOffset; } } if (inputVarIndex && !remapItems.empty()) { TVarIndexConstraintNode::TMapType result; for (auto& item: remapItems) { auto range = inputVarIndex->GetIndexMapping().equal_range(item.second); for (auto it = range.first; it != range.second; ++it) { result.push_back(std::make_pair(item.first, it->second)); } } if (!result.empty()) { ::Sort(result); input->AddConstraint(ctx.MakeConstraint(std::move(result))); } } if (!multiItems.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(multiItems))); } if (emptyOut) { input->AddConstraint(ctx.MakeConstraint()); } } else { YQL_ENSURE(input->ChildrenSize() == 4); input->CopyConstraints(*input->Child(3)); } return FromFirst(input, output, ctx); } TStatus VisitWrap(const TExprNode::TPtr& input, TExprNode::TPtr&, TExprContext& ctx) const { TStatus status = TStatus::Ok; TDynBitMap outFromChildren; // children, from which take a multi constraint for output TDynBitMap usedAlts; const auto inMulti = input->Head().GetConstraint(); for (ui32 i = 1; i < input->ChildrenSize(); ++i) { if (const auto child = input->Child(i); child->IsAtom()) { TSmallVec argConstraints(1U); if (inMulti) { const auto index = FromString(child->Content()); usedAlts.Set(index); if (const auto c = inMulti->GetItem(index)) { argConstraints.front() = c->GetAllConstraints(); outFromChildren.Set(i + 1U); } } status = status.Combine(UpdateLambdaConstraints(input->ChildRef(++i), ctx, argConstraints)); } else if (inMulti) { // Check that we can fall to default branch for (auto& item: inMulti->GetItems()) { if (!usedAlts.Test(item.first)) { outFromChildren.Set(i); break; } } } } if (status != TStatus::Ok) { return status; } if (!inMulti) { outFromChildren.Set(0, input->ChildrenSize()); } auto outType = input->GetTypeAnn(); if (auto t = GetSeqItemType(outType)) { outType = t; } if (outType->GetKind() == ETypeAnnotationKind::Variant && outType->Cast()->GetUnderlyingType()->GetKind() == ETypeAnnotationKind::Tuple) { TVector outConstraints; TVarIndexConstraintNode::TMapType remapItems; for (ui32 i = 1; i < input->ChildrenSize(); ++i) { if (input->Child(i)->IsAtom()) { ui32 index = FromString(input->Child(i)->Content()); ++i; if (outFromChildren.Test(i)) { outConstraints.push_back(&input->Child(i)->GetConstraintSet()); if (const auto outMulti = input->Child(i)->GetConstraint()) { for (auto& item: outMulti->GetItems()) { remapItems.push_back(std::make_pair(item.first, index)); } } } } else { if (outFromChildren.Test(i)) { outConstraints.push_back(&input->Child(i)->GetConstraintSet()); const auto outMulti = input->Child(i)->GetConstraint(); if (outMulti && inMulti) { for (auto& outItem: outMulti->GetItems()) { for (auto& inItem: inMulti->GetItems()) { if (!usedAlts.Test(inItem.first)) { remapItems.push_back(std::make_pair(outItem.first, inItem.first)); } } } } } } } if (auto multi = TMultiConstraintNode::MakeCommon(outConstraints, ctx)) { input->AddConstraint(multi); } if (auto empty = TEmptyConstraintNode::MakeCommon(outConstraints, ctx)) { input->AddConstraint(empty); } if (auto varIndex = input->Head().GetConstraint()) { TVarIndexConstraintNode::TMapType varIndexItems; for (auto& item: remapItems) { const auto range = varIndex->GetIndexMapping().equal_range(item.second); for (auto i = range.first; i != range.second; ++i) { varIndexItems.push_back(std::make_pair(item.first, i->second)); } } if (!varIndexItems.empty()) { ::Sort(varIndexItems); input->AddConstraint(ctx.MakeConstraint(std::move(varIndexItems))); } } } else { std::vector nonEmpty; for (ui32 i = 1; i < input->ChildrenSize(); ++i) { if (input->Child(i)->IsAtom()) { ++i; } if (outFromChildren.Test(i)) { nonEmpty.push_back(&input->Child(i)->GetConstraintSet()); } } EraseIf(nonEmpty, [] (const TConstraintSet* c) { return !!c->GetConstraint(); }); if (nonEmpty.empty()) { input->AddConstraint(ctx.MakeConstraint()); } else if (nonEmpty.size() == 1) { input->SetConstraints(*nonEmpty.front()); } if (auto varIndex = input->Head().GetConstraint()) { TVarIndexConstraintNode::TMapType varIndexItems; for (ui32 i = 1; i < input->ChildrenSize(); ++i) { if (input->Child(i)->IsAtom()) { const auto index = FromString(input->Child(i++)->Content()); if (outFromChildren.Test(i) && IsDepended(input->Child(i)->Tail(), input->Child(i)->Head().Head())) { // Somehow depends on arg const auto range = varIndex->GetIndexMapping().equal_range(index); for (auto i = range.first; i != range.second; ++i) { varIndexItems.push_back(std::make_pair(0, i->second)); } } } else if (outFromChildren.Test(i)) { if (inMulti) { for (auto& inItem: inMulti->GetItems()) { if (!usedAlts.Test(inItem.first)) { auto range = varIndex->GetIndexMapping().equal_range(inItem.first); for (auto i = range.first; i != range.second; ++i) { varIndexItems.push_back(std::make_pair(0, i->second)); } } } } } } if (!varIndexItems.empty()) { ::SortUnique(varIndexItems); input->AddConstraint(ctx.MakeConstraint(std::move(varIndexItems))); } } } return TStatus::Ok; } TStatus VariantItemWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { auto inputType = input->Head().GetTypeAnn(); if (inputType->GetKind() == ETypeAnnotationKind::Optional) { inputType = inputType->Cast()->GetItemType(); } const auto underlyingType = inputType->Cast()->GetUnderlyingType(); if (underlyingType->GetKind() == ETypeAnnotationKind::Tuple) { if (auto multi = input->Head().GetConstraint()) { std::vector nonEmpty; std::copy_if(multi->GetItems().begin(), multi->GetItems().end(), std::back_inserter(nonEmpty), [] (const TMultiConstraintNode::TMapType::value_type& v) { return !v.second.GetConstraint(); } ); if (nonEmpty.empty()) { input->AddConstraint(ctx.MakeConstraint()); } else if (nonEmpty.size() == 1) { input->SetConstraints(nonEmpty.front().second); } } if (auto varIndex = input->Head().GetConstraint()) { TVarIndexConstraintNode::TMapType varIndexItems; for (auto& item: varIndex->GetIndexMapping()) { varIndexItems.push_back(std::make_pair(0, item.second)); } if (!varIndexItems.empty()) { ::SortUnique(varIndexItems); input->AddConstraint(ctx.MakeConstraint(std::move(varIndexItems))); } } } return TStatus::Ok; } TStatus VariantWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { if (input->GetTypeAnn()->Cast()->GetUnderlyingType()->GetKind() == ETypeAnnotationKind::Tuple) { const auto index = FromString(input->Child(1)->Content()); TConstraintSet target; CopyExcept(target, input->Head().GetConstraintSet(), TVarIndexConstraintNode::Name()); input->AddConstraint(ctx.MakeConstraint(index, target)); if (auto varIndex = input->Head().GetConstraint()) { TVarIndexConstraintNode::TMapType filteredItems; for (auto& item: varIndex->GetIndexMapping()) { filteredItems.push_back(std::make_pair(index, item.second)); } input->AddConstraint(ctx.MakeConstraint(std::move(filteredItems))); } } return TStatus::Ok; } TStatus GuessWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { auto inputType = input->Head().GetTypeAnn(); if (inputType->GetKind() == ETypeAnnotationKind::Optional) { inputType = inputType->Cast()->GetItemType(); } const auto underlyingType = inputType->Cast()->GetUnderlyingType(); if (underlyingType->GetKind() == ETypeAnnotationKind::Tuple) { const auto guessIndex = FromString(input->Child(1)->Content()); if (auto multi = input->Head().GetConstraint()) { if (auto c = multi->GetItem(guessIndex)) { input->SetConstraints(*c); } else { input->AddConstraint(ctx.MakeConstraint()); } } if (auto varIndex = input->Head().GetConstraint()) { TVarIndexConstraintNode::TMapType filteredItems; for (auto& item: varIndex->GetIndexMapping()) { if (item.first == guessIndex) { filteredItems.push_back(std::make_pair(0, item.second)); } } if (!filteredItems.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(filteredItems))); } } } return TStatus::Ok; } TStatus MuxWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const auto listItemType = GetSeqItemType(input->GetTypeAnn()); if (!listItemType) { return TStatus::Ok; } if (listItemType->Cast()->GetUnderlyingType()->GetKind() == ETypeAnnotationKind::Tuple) { if (input->Head().IsList()) { TMultiConstraintNode::TMapType items; ui32 index = 0; for (auto& child: input->Head().Children()) { items.push_back(std::make_pair(index, child->GetConstraintSet())); ++index; } if (!items.empty()) { input->AddConstraint(ctx.MakeConstraint(std::move(items))); } } } return TStatus::Ok; } TStatus NthWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const auto& memberName = input->Tail().Content(); const auto& structNode = input->Head(); if (const auto emptyConstraint = structNode.GetConstraint()) { input->AddConstraint(emptyConstraint); } else { if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } if (const auto part = structNode.GetConstraint()) { if (const auto extracted = part->ExtractField(ctx, memberName)) { input->AddConstraint(extracted); } } } if (input->Head().IsList()) { input->CopyConstraints(*input->Head().Child(FromString(input->Child(1)->Content()))); } else if (input->Head().IsCallable("Demux")) { if (auto multi = input->Head().Head().GetConstraint()) { if (auto c = multi->GetItem(FromString(input->Child(1)->Content()))) { input->SetConstraints(*c); } } } return TStatus::Ok; } TStatus EquiJoinWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const auto numLists = input->ChildrenSize() - 2U; std::vector emptyInputs; TJoinLabels labels; for (auto i = 0U; i < numLists; ++i) { const auto& list = input->Child(i)->Head(); if (list.GetConstraint()) { emptyInputs.push_back(i); } if (const auto err = labels.Add(ctx, input->Child(i)->Tail(), GetSeqItemType(*list.GetTypeAnn()).Cast(), GetDetailed(list.GetConstraint(), *list.GetTypeAnn(), ctx), GetDetailed(list.GetConstraint(), *list.GetTypeAnn(), ctx))) { ctx.AddError(*err); return TStatus::Error; } } const auto joinTree = input->Child(numLists); for (auto i: emptyInputs) { if (IsRequiredSide(joinTree, labels, i).first) { input->AddConstraint(ctx.MakeConstraint()); break; } } TJoinOptions options; if (const auto status = ValidateEquiJoinOptions(input->Pos(), input->Tail(), options, ctx); status != IGraphTransformer::TStatus::Ok || options.Flatten) { return status; } const TUniqueConstraintNode* unique = nullptr; const TDistinctConstraintNode* distinct = nullptr; if (const auto status = EquiJoinConstraints(input->Pos(), unique, distinct, labels, *joinTree, ctx); status != IGraphTransformer::TStatus::Ok) { return status; } if (const auto renames = LoadJoinRenameMap(input->Tail()); !renames.empty() && (unique || distinct)) { const auto rename = [&renames](const TPartOfConstraintBase::TPathType& path) -> std::vector { if (path.empty()) return {}; const auto it = renames.find(path.front()); if (renames.cend() == it) return {path}; if (it->second.empty()) return {}; std::vector res(it->second.size()); std::transform(it->second.cbegin(), it->second.cend(), res.begin(), [&path](const std::string_view& newName) { auto newPath = path; newPath.front() = newName; return newPath; }); return res; }; if (unique) unique = unique->RenameFields(ctx, rename); if (distinct) distinct = distinct->RenameFields(ctx, rename); } if (unique) input->AddConstraint(unique->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); if (distinct) input->AddConstraint(distinct->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); return TStatus::Ok; } static std::vector GetKeys(const TExprNode& keys) { std::vector result; result.reserve(keys.ChildrenSize()); keys.ForEachChild([&result](const TExprNode& key) { result.emplace_back(key.Content()); }); return result; } template static TPartOfConstraintBase::TPathReduce GetRenames(const TExprNode& renames) { std::unordered_map map(renames.ChildrenSize() >> 1U); for (auto i = 0U; i < renames.ChildrenSize(); ++++i) map.emplace(renames.Child(i)->Content(), renames.Child(i + 1U)->Content()); return [map](const TPartOfConstraintBase::TPathType& path) -> std::vector { if constexpr (ForDict) { if (path.size() > 1U && path.front() == "1"sv) { auto out = path; out.pop_front(); if (const auto it = map.find(out.front()); map.cend() != it) { out.front() = it->second; return {std::move(out)}; } } } else { if (!path.empty()) { if (const auto it = map.find(path.front()); map.cend() != it) { auto out = path; out.front() = it->second; return {std::move(out)}; } } } return {}; }; } TStatus MapJoinCoreWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const TCoMapJoinCore core(input); const auto& joinType = core.JoinKind().Ref(); if (const auto empty = core.LeftInput().Ref().GetConstraint()) { input->AddConstraint(empty); } else if (const auto empty = core.RightDict().Ref().GetConstraint()) { if (joinType.IsAtom({"Inner", "LeftSemi"})) { input->AddConstraint(empty); } } if (joinType.IsAtom({"LeftSemi", "LeftOnly"})) { const auto rename = GetRenames(core.LeftRenames().Ref()); if (const auto unique = core.LeftInput().Ref().GetConstraint()) if (const auto renamed = unique->RenameFields(ctx, rename)) input->AddConstraint(renamed); if (const auto distinct = core.LeftInput().Ref().GetConstraint()) if (const auto renamed = distinct->RenameFields(ctx, rename)) input->AddConstraint(renamed); } else { if (const auto unique = core.LeftInput().Ref().GetConstraint()) { if (unique->ContainsCompleteSet(GetKeys(core.LeftKeysColumns().Ref())) && core.RightDict().Ref().GetTypeAnn()->Cast()->GetPayloadType()->GetKind() != ETypeAnnotationKind::List) { const auto rename = GetRenames(core.LeftRenames().Ref()); const auto rightRename = GetRenames(core.RightRenames().Ref()); auto commonUnique = unique->RenameFields(ctx, rename); if (const auto rUnique = core.RightDict().Ref().GetConstraint()) { commonUnique = TUniqueConstraintNode::Merge(commonUnique, rUnique->RenameFields(ctx, rightRename), ctx); } const auto distinct = core.LeftInput().Ref().GetConstraint(); auto commonDistinct = distinct ? distinct->RenameFields(ctx, rename) : nullptr; if (joinType.IsAtom("Inner")) { if (const auto rDistinct = core.RightDict().Ref().GetConstraint()) { commonDistinct = TDistinctConstraintNode::Merge(commonDistinct, rDistinct->RenameFields(ctx, rightRename), ctx); } } if (commonUnique) input->AddConstraint(commonUnique); if (commonDistinct) input->AddConstraint(commonDistinct); } } } if (const auto sorted = core.LeftInput().Ref().GetConstraint()) if (const auto renamed = sorted->RenameFields(ctx, GetRenames(core.LeftRenames().Ref()))) input->AddConstraint(renamed); if (const auto chopped = core.LeftInput().Ref().GetConstraint()) if (const auto renamed = chopped->RenameFields(ctx, GetRenames(core.LeftRenames().Ref()))) input->AddConstraint(renamed); return TStatus::Ok; } TExprNode::TPtr GraceJoinRightInput(const TCoGraceJoinCore& core) const { return core.RightInput().Ptr(); } TExprNode::TPtr GraceJoinRightInput(const TCoGraceSelfJoinCore& core) const { return core.Input().Ptr(); } TExprNode::TPtr GraceJoinLeftInput(const TCoGraceJoinCore& core) const { return core.LeftInput().Ptr(); } TExprNode::TPtr GraceJoinLeftInput(const TCoGraceSelfJoinCore& core) const { return core.Input().Ptr(); } template TStatus GraceJoinCoreWrapImpl(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { Y_UNUSED(output); const GraceJoinCoreType core(input); const auto& joinType = core.JoinKind().Ref(); TExprNode::TPtr leftInput = GraceJoinLeftInput(core); TExprNode::TPtr rightInput = GraceJoinRightInput(core); if (const auto lEmpty = leftInput->GetConstraint(), rEmpty = rightInput->GetConstraint(); lEmpty && rEmpty) { input->AddConstraint(ctx.MakeConstraint()); } else if (lEmpty && joinType.Content().starts_with("Left")) { input->AddConstraint(lEmpty); } else if (rEmpty && joinType.Content().starts_with("Right")) { input->AddConstraint(rEmpty); } else if ((lEmpty || rEmpty) && (joinType.IsAtom("Inner") || joinType.Content().ends_with("Semi"))) { input->AddConstraint(ctx.MakeConstraint()); } bool leftAny = false, rigthAny = false; core.Flags().Ref().ForEachChild([&](const TExprNode& flag) { if (flag.IsAtom("LeftAny")) leftAny = true; else if (flag.IsAtom("RightAny")) rigthAny = true; }); const TUniqueConstraintNode* lUnique = leftInput->GetConstraint(); const TUniqueConstraintNode* rUnique = rightInput->GetConstraint(); const bool lOneRow = lUnique && (leftAny || lUnique->ContainsCompleteSet(GetKeys(core.LeftKeysColumns().Ref()))); const bool rOneRow = rUnique && (rigthAny || rUnique->ContainsCompleteSet(GetKeys(core.RightKeysColumns().Ref()))); const bool singleSide = joinType.Content().ends_with("Semi") || joinType.Content().ends_with("Only"); if (singleSide || lOneRow || rOneRow) { const TUniqueConstraintNode* unique = nullptr; const TDistinctConstraintNode* distinct = nullptr; const bool leftSide = joinType.Content().starts_with("Left"); const bool rightSide = joinType.Content().starts_with("Right"); const auto leftRename = GetRenames(core.LeftRenames().Ref()); const auto rightRename = GetRenames(core.RightRenames().Ref()); if (singleSide) { if (leftSide && lUnique) unique = lUnique->RenameFields(ctx, leftRename); else if (rightSide && rUnique) unique = rUnique->RenameFields(ctx, rightRename); } else { const bool exclusion = joinType.IsAtom("Exclusion"); const bool useLeft = lUnique && (rOneRow || exclusion); const bool useRight = rUnique && (lOneRow || exclusion); if (useLeft && !useRight) unique = lUnique->RenameFields(ctx, leftRename); else if (useRight && !useLeft) unique = rUnique->RenameFields(ctx, rightRename); else if (useLeft && useRight) unique = TUniqueConstraintNode::Merge(lUnique->RenameFields(ctx, leftRename), rUnique->RenameFields(ctx, rightRename), ctx); } const auto lDistinct = leftInput->GetConstraint(); const auto rDistinct = rightInput->GetConstraint(); if (singleSide) { if (leftSide && lDistinct) distinct = lDistinct->RenameFields(ctx, leftRename); else if (rightSide && rDistinct) distinct = rDistinct->RenameFields(ctx, rightRename); } else { const bool inner = joinType.IsAtom("Inner"); const bool useLeft = lDistinct && rOneRow && (inner || leftSide); const bool useRight = rDistinct && lOneRow && (inner || rightSide); if (useLeft && !useRight) distinct = lDistinct->RenameFields(ctx, leftRename); else if (useRight && !useLeft) distinct = rDistinct->RenameFields(ctx, rightRename); else if (useLeft && useRight) distinct = TDistinctConstraintNode::Merge(lDistinct->RenameFields(ctx, leftRename), rDistinct->RenameFields(ctx, rightRename), ctx); } if (unique) input->AddConstraint(unique); if (distinct) input->AddConstraint(distinct); } return TStatus::Ok; } TStatus GraceJoinCoreWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { return GraceJoinCoreWrapImpl(input, output, ctx); } TStatus GraceSelfJoinCoreWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { return GraceJoinCoreWrapImpl(input, output, ctx); } template static const TUniqueConstraintNodeBase* GetForPayload(const TExprNode& input, TExprContext& ctx) { if (const auto constraint = input.GetConstraint>()) { return constraint->RenameFields(ctx, [&ctx](const TPartOfConstraintBase::TPathType& path) -> std::vector { if (path.empty() || path.front() != ctx.GetIndexAsString(1U)) return {}; auto copy = path; copy.pop_front(); return {copy}; }); } return nullptr; } TStatus JoinDictWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const TCoJoinDict join(input); const auto& joinType = join.JoinKind().Ref(); if (const auto lEmpty = join.LeftInput().Ref().GetConstraint(), rEmpty = join.RightInput().Ref().GetConstraint(); lEmpty && rEmpty) { input->AddConstraint(ctx.MakeConstraint()); } else if (lEmpty && joinType.Content().starts_with("Left")) { input->AddConstraint(lEmpty); } else if (rEmpty && joinType.Content().starts_with("Right")) { input->AddConstraint(rEmpty); } else if ((lEmpty || rEmpty) && (joinType.IsAtom("Inner") || joinType.Content().ends_with("Semi"))) { input->AddConstraint(ctx.MakeConstraint()); } bool lOneRow = false, rOneRow = false; if (const auto& flags = join.Flags()) { flags.Cast().Ref().ForEachChild([&](const TExprNode& flag) { lOneRow = lOneRow || flag.IsAtom("LeftUnique"); rOneRow = rOneRow || flag.IsAtom("RightUnique"); }); } const auto lUnique = GetForPayload(join.LeftInput().Ref(), ctx); const auto rUnique = GetForPayload(join.RightInput().Ref(), ctx); const auto lDistinct = GetForPayload(join.LeftInput().Ref(), ctx); const auto rDistinct = GetForPayload(join.RightInput().Ref(), ctx); const bool leftSide = joinType.Content().starts_with("Left"); const bool rightSide = joinType.Content().starts_with("Right"); if (joinType.Content().ends_with("Semi") || joinType.Content().ends_with("Only")) { if (leftSide) { if (lUnique) input->AddConstraint(lUnique); if (lDistinct) input->AddConstraint(lDistinct); } else if (rightSide) { if (rUnique) input->AddConstraint(rUnique); if (rDistinct) input->AddConstraint(rDistinct); } } else if (lOneRow || rOneRow) { const auto rename = [](const std::string_view& prefix, TPartOfConstraintBase::TPathType path) { path.emplace_front(prefix); return std::vector(1U, std::move(path)); }; const auto leftRename = std::bind(rename, ctx.GetIndexAsString(0U), std::placeholders::_1); const auto rightRename = std::bind(rename, ctx.GetIndexAsString(1U), std::placeholders::_1); if (lUnique || rUnique) { const TUniqueConstraintNode* unique = nullptr; const bool exclusion = joinType.IsAtom("Exclusion"); const bool useLeft = lUnique && (rOneRow || exclusion); const bool useRight = rUnique && (lOneRow || exclusion); if (useLeft && !useRight) unique = lUnique->RenameFields(ctx, leftRename); else if (useRight && !useLeft) unique = rUnique->RenameFields(ctx, rightRename); else if (useLeft && useRight) unique = TUniqueConstraintNode::Merge(lUnique->RenameFields(ctx, leftRename), rUnique->RenameFields(ctx, rightRename), ctx); if (unique) input->AddConstraint(unique); } if (lDistinct || rDistinct) { const TDistinctConstraintNode* distinct = nullptr; const bool inner = joinType.IsAtom("Inner"); const bool useLeft = lDistinct && rOneRow && (inner || leftSide); const bool useRight = rDistinct && lOneRow && (inner || rightSide); if (useLeft && !useRight) distinct = lDistinct->RenameFields(ctx, leftRename); else if (useRight && !useLeft) distinct = rDistinct->RenameFields(ctx, rightRename); else if (useLeft && useRight) distinct = TDistinctConstraintNode::Merge(lDistinct->RenameFields(ctx, leftRename), rDistinct->RenameFields(ctx, rightRename), ctx); if (distinct) input->AddConstraint(distinct); } } return TStatus::Ok; } TStatus IsKeySwitchWrap(const TExprNode::TPtr& input, TExprNode::TPtr& /*output*/, TExprContext& ctx) const { const TCoIsKeySwitch keySwitch(input); TSmallVec itemConstraints, stateConstraints; itemConstraints.emplace_back(keySwitch.Item().Ref().GetAllConstraints()); stateConstraints.emplace_back(keySwitch.State().Ref().GetAllConstraints()); return UpdateLambdaConstraints(input->ChildRef(TCoIsKeySwitch::idx_ItemKeyExtractor), ctx, itemConstraints) .Combine(UpdateLambdaConstraints(input->ChildRef(TCoIsKeySwitch::idx_StateKeyExtractor), ctx, stateConstraints)); } template static TPartOfConstraintBase::TSetType GetSimpleKeys(const TExprNode& node, const TExprNode::TChildrenType& args, TExprContext& ctx) { TPartOfConstraintBase::TSetType keys; if (node.IsCallable("AggrNotEquals")) { const TExprNode& body = node.Head().IsCallable("StablePickle") ? node.Head() : node; if (body.Head().IsList() && body.Tail().IsList() && body.Head().ChildrenSize() == body.Tail().ChildrenSize()) { keys.reserve(body.Tail().ChildrenSize()); for (auto i = 0U; i < body.Head().ChildrenSize(); ++i){ if (auto l = GetPathToKey(*body.Head().Child(i), args), r = GetPathToKey(*body.Tail().Child(i), args); l && r && *l == *r) { if constexpr (Wide) { auto path = r->first; path.emplace_front(ctx.GetIndexAsString(r->second)); } else { YQL_ENSURE(l->second == 0U, "Unexpected arg index: " << l->second); keys.insert_unique(l->first); } } } } else if (auto l = GetPathToKey(body.Head(), args), r = GetPathToKey(body.Tail(), args); l && r && *l == *r) { if constexpr (Wide) { auto path = l->first; path.emplace_front(ctx.GetIndexAsString(l->second)); } else { YQL_ENSURE(r->second == 0U, "Unexpected arg index: " << r->second); keys.insert_unique(r->first); } } } else if (node.IsCallable("Or")) { keys.reserve(node.ChildrenSize()); for (auto i = 0U; i < node.ChildrenSize(); ++i) { const auto& part = GetSimpleKeys(*node.Child(i), args, ctx); keys.insert_unique(part.cbegin(), part.cend()); } } return keys; } template static TPartOfConstraintBase::TSetType GetSimpleKeys(const TExprNode& selector, TExprContext& ctx) { YQL_ENSURE(selector.IsLambda() && 2U == selector.ChildrenSize()); const auto& body = selector.Tail(); if constexpr (!Wide) { if (TCoIsKeySwitch::Match(&body)) { const TCoIsKeySwitch keySwitch(&body); const auto& i = GetPathsToKeys(*ctx.ReplaceNode(keySwitch.ItemKeyExtractor().Body().Ptr(), keySwitch.ItemKeyExtractor().Args().Arg(0).Ref(), keySwitch.Item().Ptr()), keySwitch.Item().Ref()); const auto& s = GetPathsToKeys(*ctx.ReplaceNode(keySwitch.StateKeyExtractor().Body().Ptr(), keySwitch.StateKeyExtractor().Args().Arg(0).Ref(), keySwitch.State().Ptr()), keySwitch.Item().Ref()); return i == s ? i : TPartOfConstraintBase::TSetType(); } } return GetSimpleKeys(selector.Tail(), selector.Head().Children(), ctx); } static TExprNode::TPtr FuseInitLambda(const TExprNode& inner, const TExprNode& outer, TExprContext& ctx) { YQL_ENSURE(outer.IsLambda() && inner.IsLambda()); const auto& outerArgs = outer.Head(); const auto& innerArgs = inner.Head(); YQL_ENSURE(outerArgs.ChildrenSize() + 1U == innerArgs.ChildrenSize() + inner.ChildrenSize()); TNodeOnNodeOwnedMap outerReplaces(outerArgs.ChildrenSize()); auto i = 0U; for (auto& item : innerArgs.ChildrenList()) YQL_ENSURE(outerReplaces.emplace(outerArgs.Child(i++), std::move(item)).second); for (auto& item : GetLambdaBody(inner)) YQL_ENSURE(outerReplaces.emplace(outerArgs.Child(i++), std::move(item)).second); return ctx.NewLambda(outer.Pos(), inner.HeadPtr(), ctx.ReplaceNodes(GetLambdaBody(outer), outerReplaces)); } TStatus CondenseWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { auto argsConstraints = GetConstraintsForInputArgument(*input, ctx); const auto initState = input->Child(1); argsConstraints.emplace_back(initState->GetAllConstraints()); if (const auto status = UpdateLambdaConstraints(input->ChildRef(2), ctx, argsConstraints) .Combine(UpdateLambdaConstraints(input->TailRef(), ctx, argsConstraints)); status != TStatus::Ok) { return status; } return FromFirst(input, output, ctx); } template static void GetCommonFromBothLambdas(const TExprNode::TPtr& input, const typename TConstraint::TMainConstraint* original, TExprContext& ctx) { if (original) if (const auto initPart = GetConstraintFromLambda(*input->Child(1), ctx)) if (const auto init = TConstraint::MakeComplete(ctx, initPart->GetColumnMapping(), original)) if (const auto updatePart = GetConstraintFromLambda(input->Tail(), ctx)) if (const auto update = TConstraint::MakeComplete(ctx, updatePart->GetColumnMapping(), original)) if (const auto common = init->MakeCommon(update, ctx)) input->AddConstraint(common->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); } template TStatus Condense1Wrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { auto argsConstraints = GetConstraintsForInputArgument(*input, ctx); const auto initLambda = input->Child(1); const auto switchLambda = input->Child(2); const TUniqueConstraintNode* unique = nullptr; const TDistinctConstraintNode* distinct = nullptr; const auto sorted = input->Head().GetConstraint(); const auto chopped = input->Head().GetConstraint(); if (sorted || chopped) { if (const auto& keys = GetSimpleKeys(*FuseInitLambda(*initLambda, *switchLambda, ctx), ctx); !keys.empty()) { if (sorted && (sorted->StartsWith(keys) || sorted->GetSimplifiedForType(*input->Head().GetTypeAnn(), ctx)->StartsWith(keys)) || chopped && (chopped->Equals(keys) || chopped->GetSimplifiedForType(*input->Head().GetTypeAnn(), ctx)->Equals(keys))) { TPartOfConstraintBase::TSetOfSetsType sets; sets.reserve(keys.size()); for (const auto& key : keys) sets.insert_unique(TPartOfConstraintBase::TSetType{key}); unique = ctx.MakeConstraint(TUniqueConstraintNode::TContentType{sets})->GetComplicatedForType(*input->Head().GetTypeAnn(), ctx); distinct = ctx.MakeConstraint(TDistinctConstraintNode::TContentType{sets})->GetComplicatedForType(*input->Head().GetTypeAnn(), ctx); if constexpr (Wide) { if (const auto& mapping = TPartOfUniqueConstraintNode::GetCommonMapping(unique); !mapping.empty()) { for (ui32 i = 0U; i < argsConstraints.size(); ++i) { if (auto extracted = TPartOfUniqueConstraintNode::ExtractField(mapping, ctx.GetIndexAsString(i)); !extracted.empty()) { argsConstraints[i].emplace_back(ctx.MakeConstraint(std::move(extracted))); } } } if (const auto& mapping = TPartOfDistinctConstraintNode::GetCommonMapping(distinct); !mapping.empty()) { for (ui32 i = 0U; i < argsConstraints.size(); ++i) { if (auto extracted = TPartOfDistinctConstraintNode::ExtractField(mapping, ctx.GetIndexAsString(i)); !extracted.empty()) { argsConstraints[i].emplace_back(ctx.MakeConstraint(std::move(extracted))); } } } } else { argsConstraints.front().emplace_back(ctx.MakeConstraint(TPartOfUniqueConstraintNode::GetCommonMapping(unique))); argsConstraints.front().emplace_back(ctx.MakeConstraint(TPartOfDistinctConstraintNode::GetCommonMapping(distinct))); } } } } if (const auto status = UpdateLambdaConstraints(input->ChildRef(1), ctx, argsConstraints); status != TStatus::Ok) { return status; } argsConstraints.reserve(argsConstraints.size() + initLambda->ChildrenSize() - 1U); for (ui32 i = 1U; i < initLambda->ChildrenSize(); ++i) { argsConstraints.emplace_back(initLambda->Child(i)->GetAllConstraints()); } if (const auto status = UpdateLambdaConstraints(input->ChildRef(2), ctx, argsConstraints) .Combine(UpdateLambdaConstraints(input->TailRef(), ctx, argsConstraints)); status != TStatus::Ok) { return status; } GetCommonFromBothLambdas(input, unique, ctx); GetCommonFromBothLambdas(input, distinct, ctx); return FromFirst(input, output, ctx); } template static void GetCommonFromBothLambdas(const TExprNode::TPtr& input, TExprContext& ctx) { if (const auto original = input->Head().GetConstraint()) if (const auto initPart = GetConstraintFromLambda(*input->Child(1), ctx)) if (const auto init = TConstraint::MakeComplete(ctx, initPart->GetColumnMapping(), original)) if (const auto updatePart = GetConstraintFromLambda(input->Tail(), ctx)) if (const auto update = TConstraint::MakeComplete(ctx, updatePart->GetColumnMapping(), original)) if (const auto common = init->MakeCommon(update, ctx)) input->AddConstraint(common->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); } template TStatus Chain1MapWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { auto argsConstraints = GetConstraintsForInputArgument(*input, ctx); if (const auto status = UpdateLambdaConstraints(input->ChildRef(1), ctx, argsConstraints); status != TStatus::Ok) { return status; } const auto initLambda = input->Child(1); argsConstraints.reserve(argsConstraints.size() + initLambda->ChildrenSize() - 1U); for (ui32 i = 1U; i < initLambda->ChildrenSize(); ++i) { argsConstraints.emplace_back(initLambda->Child(i)->GetAllConstraints()); } if (const auto status = UpdateLambdaConstraints(input->ChildRef(2), ctx, argsConstraints); status != TStatus::Ok) { return status; } GetCommonFromBothLambdas(input, ctx); GetCommonFromBothLambdas(input, ctx); GetCommonFromBothLambdas(input, ctx); GetCommonFromBothLambdas(input, ctx); return FromFirst(input, output, ctx); } template static void GetUniquesForPayloads(const TExprNode::TPtr& input, TExprContext& ctx) { typename TUniqueConstraintNodeBase::TContentType content{TPartOfConstraintBase::TSetOfSetsType{TPartOfConstraintBase::TSetType{TPartOfConstraintBase::TPathType{ctx.GetIndexAsString(0U)}}}}; if (const auto lambda = GetConstraintFromLambda>, false>(*input->Child(2), ctx)) { if (const auto original = GetDetailed(input->Head().GetConstraint>(), *input->Head().GetTypeAnn(), ctx)) { if (const auto complete = TPartOfConstraintNode>::MakeComplete(ctx, lambda->GetColumnMapping(), original, ctx.GetIndexAsString(1U))) { content.insert_unique(complete->GetContent().cbegin(), complete->GetContent().cend()); } } } input->AddConstraint(ctx.MakeConstraint>(std::move(content))); } TStatus ToDictWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { const auto argsConstraints = GetConstraintsForInputArgument(*input, ctx); if (const auto status = UpdateLambdaConstraints(input->ChildRef(1), ctx, argsConstraints); status != TStatus::Ok) { return status; } if (const auto status = UpdateLambdaConstraints(input->ChildRef(2), ctx, argsConstraints); status != TStatus::Ok) { return status; } GetUniquesForPayloads(input, ctx); GetUniquesForPayloads(input, ctx); return FromFirst(input, output, ctx); } TStatus DictItemsWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (const auto unique = input->Head().GetConstraint()) input->AddConstraint(unique->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); if (const auto distinct = input->Head().GetConstraint()) input->AddConstraint(distinct->GetSimplifiedForType(*input->GetTypeAnn(), ctx)); return FromFirst(input, output, ctx); } template TStatus DictHalfWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { const auto& side = ctx.GetIndexAsString(Keys ? 0U : 1U); const auto reduce = [&side](const TPartOfConstraintBase::TPathType& path) -> std::vector { if (path.empty() || path.front() != side) return {}; auto copy = path; copy.pop_front(); return {copy}; }; ReduceFromHead(input, reduce, ctx); ReduceFromHead(input, reduce, ctx); ReduceFromHead(input, reduce, ctx); ReduceFromHead(input, reduce, ctx); return FromFirst(input, output, ctx); } template TStatus ShuffleByKeysWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { using TCoBase = std::conditional_t; if (const auto status = UpdateLambdaConstraints(*input->Child(TCoBase::idx_KeySelectorLambda)); status != TStatus::Ok) { return status; } TPartOfConstraintBase::TSetType keys; if constexpr (Partitions) { keys = GetPathsToKeys(input->Child(TCoBase::idx_KeySelectorLambda)->Tail(), input->Child(TCoBase::idx_KeySelectorLambda)->Head().Head()); if (const auto sortKeySelector = input->Child(TCoBase::idx_SortKeySelectorLambda); sortKeySelector->IsLambda()) { if (const auto status = UpdateLambdaConstraints(*sortKeySelector); status != TStatus::Ok) { return status; } auto sortKeys = GetPathsToKeys(sortKeySelector->Tail(), sortKeySelector->Head().Head()); std::move(sortKeys.begin(), sortKeys.end(), std::back_inserter(keys)); std::sort(keys.begin(), keys.end()); } } const auto filter = [](const std::string_view& name) { return name == TEmptyConstraintNode::Name() || name == TUniqueConstraintNode::Name() || name == TDistinctConstraintNode::Name(); }; TConstraintNode::TListType argConstraints; const auto source = input->Child(TCoBase::idx_Input); std::copy_if(source->GetAllConstraints().cbegin(), source->GetAllConstraints().cend(), std::back_inserter(argConstraints), std::bind(filter, std::bind(&TConstraintNode::GetName, std::placeholders::_1))); if (const auto multi = source->template GetConstraint()) if (const auto filtered = multi->FilterConstraints(ctx, filter)) argConstraints.emplace_back(filtered); if constexpr (Partitions) { if (!keys.empty()) argConstraints.emplace_back(ctx.MakeConstraint(keys)->GetComplicatedForType(*input->Head().GetTypeAnn(), ctx)); } if (const auto status = UpdateLambdaConstraints(input->ChildRef(TCoBase::idx_ListHandlerLambda), ctx, {argConstraints}); status != TStatus::Ok) { return status; } const auto handlerLambda = input->Child(TCoBase::idx_ListHandlerLambda); if (const auto unique = handlerLambda->template GetConstraint()) input->AddConstraint(unique); if (const auto distinct = handlerLambda->template GetConstraint()) input->AddConstraint(distinct); const bool multiInput = ETypeAnnotationKind::Variant == GetSeqItemType(*input->Head().GetTypeAnn()).GetKind(); const auto lambdaVarIndex = handlerLambda->template GetConstraint(); const auto multi = input->Head().template GetConstraint(); const auto lambdaMulti = handlerLambda->template GetConstraint(); if (const auto varIndex = input->Head().template GetConstraint()) { if (multiInput) { if (lambdaVarIndex) { if (auto outVarIndex = GetVarIndexOverVarIndexConstraint(*varIndex, *lambdaVarIndex, ctx)) { input->AddConstraint(outVarIndex); } } } else { if (lambdaMulti) { TVarIndexConstraintNode::TMapType remapItems; for (auto& multiItem: lambdaMulti->GetItems()) { for (auto& varItem: varIndex->GetIndexMapping()) { remapItems.push_back(std::make_pair(multiItem.first, varItem.second)); } } if (!remapItems.empty()) { ::SortUnique(remapItems); input->AddConstraint(ctx.MakeConstraint(std::move(remapItems))); } } else { input->AddConstraint(varIndex); } } } if (lambdaMulti && !input->Head().GetConstraint()) { TMultiConstraintNode::TMapType remappedItems; for (const auto& item: lambdaMulti->GetItems()) { remappedItems.push_back(std::make_pair(item.first, TConstraintSet{})); if (!multiInput) { // remapping one to many if (const auto empty = item.second.template GetConstraint()) remappedItems.pop_back(); else { if (const auto unique = item.second.template GetConstraint()) remappedItems.back().second.AddConstraint(unique); if (const auto distinct = item.second.template GetConstraint()) remappedItems.back().second.AddConstraint(distinct); } } else if (lambdaVarIndex && multi) { const auto range = lambdaVarIndex->GetIndexMapping().equal_range(item.first); switch (std::distance(range.first, range.second)) { case 0: // new index break; case 1: // remapping 1 to 1 if (auto origConstr = multi->GetItem(range.first->second)) { if (const auto empty = item.second.template GetConstraint()) remappedItems.pop_back(); else { if (const auto unique = item.second.template GetConstraint()) remappedItems.back().second.AddConstraint(unique); if (const auto distinct = item.second.template GetConstraint()) remappedItems.back().second.AddConstraint(distinct); } } else { remappedItems.pop_back(); } break; default: // remapping many to one { std::vector nonEmpty; for (auto i = range.first; i != range.second; ++i) { if (auto origConstr = multi->GetItem(i->second)) { nonEmpty.push_back(origConstr); } } EraseIf(nonEmpty, [] (const TConstraintSet* c) { return !!c->GetConstraint(); }); if (nonEmpty.empty()) { remappedItems.back().second.AddConstraint(ctx.MakeConstraint()); } else if (nonEmpty.size() == 1) { remappedItems.back().second = std::move(*nonEmpty.front()); } } } } else { remappedItems.back().second = item.second; } } if (remappedItems) { input->AddConstraint(ctx.MakeConstraint(std::move(remappedItems))); } } else if (multi && lambdaVarIndex) { // Many to one const auto range = lambdaVarIndex->GetIndexMapping().equal_range(0); std::vector nonEmpty; for (auto i = range.first; i != range.second; ++i) { if (auto origConstr = multi->GetItem(i->second)) { nonEmpty.push_back(origConstr); } } EraseIf(nonEmpty, [] (const TConstraintSet* c) { return !!c->GetConstraint(); }); if (nonEmpty.empty()) { input->AddConstraint(ctx.MakeConstraint()); } else if (nonEmpty.size() == 1) { input->SetConstraints(*nonEmpty.front()); } } TApplyConstraintFromInput::Do(input); return FromFirst(input, output, ctx); } template TStatus AggregateWrap(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) const { if (HasSetting(input->Tail(), "session")) { // TODO: support sessions return TStatus::Ok; } if (const auto size = input->Child(1)->ChildrenSize()) { if constexpr (Final) { bool allKeysInOutput = true; if (auto outputColumnsSetting = GetSetting(input->Tail(), "output_columns")) { THashSet outputColumns; for (auto& col : outputColumnsSetting->Child(1)->Children()) { YQL_ENSURE(col->IsAtom()); outputColumns.insert(col->Content()); } allKeysInOutput = AllOf(input->Child(1)->Children(), [&](const auto& key) { return outputColumns.contains(key->Content()); }); } if (allKeysInOutput) { std::vector columns; columns.reserve(size); for (const auto& child: input->Child(1)->Children()) { columns.emplace_back(child->Content()); } input->AddConstraint(ctx.MakeConstraint(columns)); input->AddConstraint(ctx.MakeConstraint(columns)); } } return FromFirst(input, output, ctx); } return TStatus::Ok; } TStatus FoldWrap(const TExprNode::TPtr& input, TExprNode::TPtr&, TExprContext& ctx) const { const TStructExprType* inItemType = GetNonEmptyStructItemType(*input->Head().GetTypeAnn()); const TStructExprType* outItemType = GetNonEmptyStructItemType(*input->GetTypeAnn()); if (!inItemType || !outItemType) { return UpdateAllChildLambdasConstraints(*input); } if (input->Child(1)->IsLambda()) { TConstraintNode::TListType argConstraints; if (const auto status = UpdateLambdaConstraints(input->ChildRef(1), ctx, {argConstraints}); status != TStatus::Ok) { return status; } } const auto initState = input->Child(1); auto stateConstraints = initState->GetAllConstraints(); stateConstraints.erase( std::remove_if( stateConstraints.begin(), stateConstraints.end(), [](const TConstraintNode* c) { return c->GetName() == TEmptyConstraintNode::Name(); } ), stateConstraints.end() ); TConstraintNode::TListType argConstraints; if (const auto status = UpdateLambdaConstraints(input->TailRef(), ctx, {argConstraints, stateConstraints}); status != TStatus::Ok) { return status; } return TStatus::Ok; } TStatus MultiHoppingCoreWrap(const TExprNode::TPtr& input, TExprNode::TPtr&, TExprContext& ctx) const { if (const auto status = UpdateAllChildLambdasConstraints(*input); status != TStatus::Ok) { return status; } TExprNode::TPtr keySelectorLambda = input->Child(TCoMultiHoppingCore::idx_KeyExtractor); const auto keys = GetPathsToKeys(keySelectorLambda->Tail(), keySelectorLambda->Head().Head()); std::vector columns(keys.size()); std::transform(keys.begin(), keys.end(), columns.begin(), [](const TPartOfConstraintBase::TPathType& path) -> std::string_view { return path.front(); }); if (!columns.empty()) { input->AddConstraint(ctx.MakeConstraint(columns)); input->AddConstraint(ctx.MakeConstraint(columns)); } return TStatus::Ok; } private: template static void CopyExcept(TConstraintContainer& dst, const TConstraintContainer& from, const TSet& except) { for (auto c: from.GetAllConstraints()) { if (!except.contains(c->GetName())) { dst.AddConstraint(c); } } } template static void CopyExcept(TConstraintContainer& dst, const TConstraintContainer& from, TStringBuf except) { for (auto c: from.GetAllConstraints()) { if (c->GetName() != except) { dst.AddConstraint(c); } } } static void ExtractKeys(const TExprNode& keySelectorLambda, TVector& columns) { const auto arg = keySelectorLambda.Head().Child(0); auto body = keySelectorLambda.Child(1); if (body->IsCallable("StablePickle")) { body = body->Child(0); } ExtractSimpleKeys(body, arg, columns); } template static const TConstraintWithFields* GetDetailed(const TConstraintWithFields* constraint, const TTypeAnnotationNode& type, TExprContext& ctx) { return constraint ? constraint->GetComplicatedForType(type, ctx) : nullptr; } static const TStructExprType* GetNonEmptyStructItemType(const TTypeAnnotationNode& type) { const auto itemType = GetSeqItemType(&type); if (!itemType || itemType->GetKind() != ETypeAnnotationKind::Struct) { return nullptr; } const TStructExprType* structType = itemType->Cast(); return structType->GetSize() ? structType : nullptr; } static const TSortedConstraintNode* DeduceSortConstraint(const TExprNode& directions, const TExprNode& keyExtractor, TExprContext& ctx) { if (const auto& columns = ExtractSimpleSortTraits(directions, keyExtractor); !columns.empty()) { TSortedConstraintNode::TContainerType content(columns.size()); std::transform(columns.cbegin(), columns.cend(), content.begin(), [](const std::pair& item) { return std::make_pair(TSortedConstraintNode::TSetType{item.first}, item.second); }); return ctx.MakeConstraint(std::move(content)); } return nullptr; } static const TVarIndexConstraintNode* GetVarIndexOverVarIndexConstraint(const TVarIndexConstraintNode& inputVarIndex, const TVarIndexConstraintNode& varIndex, TExprContext& ctx) { TVarIndexConstraintNode::TMapType result; for (auto& item: varIndex.GetIndexMapping()) { auto range = inputVarIndex.GetIndexMapping().equal_range(item.second); for (auto it = range.first; it != range.second; ++it) { result.push_back(std::make_pair(item.first, it->second)); } } if (!result.empty()) { return ctx.MakeConstraint(std::move(result)); } return nullptr; } static const TExprNode* SkipModifiers(const TExprNode* valueNode) { if (TCoJust::Match(valueNode)) { return SkipModifiers(valueNode->Child(0)); } if (TCoUnwrap::Match(valueNode)) { return SkipModifiers(valueNode->Child(0)); } return valueNode; } private: const bool SubGraph; std::unordered_map Functions; }; template<> const TPartOfSortedConstraintNode* TCallableConstraintTransformer::GetConstraintFromWideResultLambda(const TExprNode& lambda, TExprContext& ctx) { TPartOfSortedConstraintNode::TMapType sorted; for (auto i = 1U; i < lambda.ChildrenSize(); ++i) { if (const auto part = lambda.Child(i)->GetConstraint()) TPartOfSortedConstraintNode::UniqueMerge(sorted, part->GetColumnMapping(ctx.GetIndexAsString(i - 1U))); } return sorted.empty() ? nullptr : ctx.MakeConstraint(std::move(sorted)); } template<> const TPartOfChoppedConstraintNode* TCallableConstraintTransformer::GetConstraintFromWideResultLambda(const TExprNode& lambda, TExprContext& ctx) { TPartOfChoppedConstraintNode::TMapType chopped; for (auto i = 1U; i < lambda.ChildrenSize(); ++i) { if (const auto part = lambda.Child(i)->GetConstraint()) TPartOfChoppedConstraintNode::UniqueMerge(chopped, part->GetColumnMapping(ctx.GetIndexAsString(i - 1U))); } return chopped.empty() ? nullptr : ctx.MakeConstraint(std::move(chopped)); } template<> const TPartOfUniqueConstraintNode* TCallableConstraintTransformer::GetConstraintFromWideResultLambda(const TExprNode& lambda, TExprContext& ctx) { TPartOfUniqueConstraintNode::TMapType uniques; for (auto i = 1U; i < lambda.ChildrenSize(); ++i) { if (const auto part = lambda.Child(i)->GetConstraint()) TPartOfUniqueConstraintNode::UniqueMerge(uniques, part->GetColumnMapping(ctx.GetIndexAsString(i - 1U))); } return uniques.empty() ? nullptr : ctx.MakeConstraint(std::move(uniques)); } template<> const TPartOfDistinctConstraintNode* TCallableConstraintTransformer::GetConstraintFromWideResultLambda(const TExprNode& lambda, TExprContext& ctx) { TPartOfDistinctConstraintNode::TMapType uniques; for (auto i = 1U; i < lambda.ChildrenSize(); ++i) { if (const auto part = lambda.Child(i)->GetConstraint()) TPartOfDistinctConstraintNode::UniqueMerge(uniques, part->GetColumnMapping(ctx.GetIndexAsString(i - 1U))); } return uniques.empty() ? nullptr : ctx.MakeConstraint(std::move(uniques)); } template<> const TVarIndexConstraintNode* TCallableConstraintTransformer::TCallableConstraintTransformer::GetConstraintFromWideResultLambda(const TExprNode& lambda, TExprContext& ctx) { TVector structConstraints; structConstraints.reserve(lambda.ChildrenSize() - 1U); for (auto i = 1U; i < lambda.ChildrenSize(); ++i) { auto valueNode = lambda.Child(i); if (TCoCoalesce::Match(valueNode)) { if (valueNode->Head().GetTypeAnn()->GetKind() != ETypeAnnotationKind::Optional || valueNode->ChildrenSize() == 1) { valueNode = valueNode->Child(0); } } if (TCoJust::Match(valueNode)) { valueNode = valueNode->Child(0); } if (TCoMember::Match(valueNode) || TCoNth::Match(valueNode)) { structConstraints.push_back(&valueNode->Head().GetConstraintSet()); } else if (valueNode->Type() == TExprNode::Argument) { structConstraints.push_back(&valueNode->GetConstraintSet()); } } return TVarIndexConstraintNode::MakeCommon(structConstraints, ctx); } template const TConstraint* TCallableConstraintTransformer::GetConstraintFromWideResultLambda(const TExprNode&, TExprContext&) { return nullptr; } class TDefaultCallableConstraintTransformer : public TSyncTransformerBase { public: TStatus DoTransform(TExprNode::TPtr input, TExprNode::TPtr& output, TExprContext& ctx) override { Y_UNUSED(output); Y_UNUSED(ctx); return UpdateAllChildLambdasConstraints(*input); } void Rewind() final { } }; template class TConstraintTransformer : public TGraphTransformerBase { public: TConstraintTransformer(TAutoPtr callableTransformer, TTypeAnnotationContext& types) : CallableTransformer(callableTransformer) , Types(types) { } ~TConstraintTransformer() = default; TStatus DoTransform(TExprNode::TPtr input, TExprNode::TPtr& output, TExprContext& ctx) final { YQL_PROFILE_SCOPE(DEBUG, "ConstraintTransformer::DoTransform"); output = input; auto status = TransformNode(input, output, ctx); UpdateStatusIfChanged(status, input, output); if (status.Level != TStatus::Error && HasRenames) { output = ctx.ReplaceNodes(std::move(output), Processed); } Processed.clear(); if (status == TStatus::Ok) { Types.ExpectedConstraints.clear(); } HasRenames = false; return status; } NThreading::TFuture DoGetAsyncFuture(const TExprNode& input) final { YQL_PROFILE_SCOPE(DEBUG, "ConstraintTransformer::DoGetAsyncFuture"); Y_UNUSED(input); TVector> futures; for (const auto& callable : CallableInputs) { futures.push_back(CallableTransformer->GetAsyncFuture(*callable)); } return WaitExceptionOrAll(futures); } TStatus DoApplyAsyncChanges(TExprNode::TPtr input, TExprNode::TPtr& output, TExprContext& ctx) final { YQL_PROFILE_SCOPE(DEBUG, "ConstraintTransformer::DoApplyAsyncChanges"); output = input; TStatus combinedStatus = TStatus::Ok; for (const auto& callable : CallableInputs) { callable->SetState(TExprNode::EState::ConstrPending); TExprNode::TPtr callableOutput; auto status = CallableTransformer->ApplyAsyncChanges(callable, callableOutput, ctx); Y_ABORT_UNLESS(callableOutput); YQL_ENSURE(status != TStatus::Async); YQL_ENSURE(callableOutput == callable); combinedStatus = combinedStatus.Combine(status); if (status.Level == TStatus::Error) { callable->SetState(TExprNode::EState::Error); } } CallableInputs.clear(); if (combinedStatus.Level == TStatus::Ok) { Processed.clear(); } return combinedStatus; } void Rewind() final { CallableTransformer->Rewind(); CallableInputs.clear(); Processed.clear(); HasRenames = false; CurrentFunctions = {}; } private: TStatus TransformNode(const TExprNode::TPtr& start, TExprNode::TPtr& output, TExprContext& ctx) { output = start; auto processedPair = Processed.emplace(start.Get(), nullptr); // by default node is not changed if (!processedPair.second) { if (processedPair.first->second) { output = processedPair.first->second; return TStatus::Repeat; } switch (start->GetState()) { case TExprNode::EState::Initial: case TExprNode::EState::TypeInProgress: case TExprNode::EState::TypePending: return TStatus(TStatus::Repeat, true); case TExprNode::EState::TypeComplete: break; case TExprNode::EState::ConstrInProgress: return IGraphTransformer::TStatus::Async; case TExprNode::EState::ConstrPending: if (start->Type() == TExprNode::Lambda) { if (start->Head().GetState() != TExprNode::EState::ConstrComplete) { return TStatus::Ok; } else if (start->Head().ChildrenSize() == 0) { break; } } if (start->Type() == TExprNode::Arguments || start->Type() == TExprNode::Argument) { break; } return TStatus(TStatus::Repeat, true); case TExprNode::EState::ConstrComplete: case TExprNode::EState::ExecutionInProgress: case TExprNode::EState::ExecutionRequired: case TExprNode::EState::ExecutionPending: case TExprNode::EState::ExecutionComplete: return TStatus::Ok; case TExprNode::EState::Error: return TStatus::Error; default: YQL_ENSURE(false, "Unknown state"); } } auto input = start; for (;;) { TIssueScopeGuard issueScope(ctx.IssueManager, [this, input, &ctx]() -> TIssuePtr { TStringBuilder str; str << "At "; switch (input->Type()) { case TExprNode::Callable: if (!CurrentFunctions.empty() && CurrentFunctions.top().second) { return nullptr; } if (!CurrentFunctions.empty()) { CurrentFunctions.top().second = true; } str << "function: " << input->Content(); break; case TExprNode::List: if (CurrentFunctions.empty()) { str << "tuple"; } else if (!CurrentFunctions.top().second) { CurrentFunctions.top().second = true; str << "function: " << CurrentFunctions.top().first; } else { return nullptr; } break; case TExprNode::Lambda: if (CurrentFunctions.empty()) { str << "lambda"; } else if (!CurrentFunctions.top().second) { CurrentFunctions.top().second = true; str << "function: " << CurrentFunctions.top().first; } else { return nullptr; } break; default: str << "unknown"; } return MakeIntrusive(ctx.GetPosition(input->Pos()), str); }); if (input->IsCallable()) { CurrentFunctions.emplace(input->Content(), false); } Y_SCOPE_EXIT(this, input) { if (input->IsCallable()) { CurrentFunctions.pop(); if (!CurrentFunctions.empty() && CurrentFunctions.top().first.ends_with('!')) { CurrentFunctions.top().second = true; } } }; TStatus retStatus = TStatus::Error; switch (input->GetState()) { case TExprNode::EState::Initial: case TExprNode::EState::TypeInProgress: case TExprNode::EState::TypePending: return TStatus(TStatus::Repeat, true); case TExprNode::EState::TypeComplete: case TExprNode::EState::ConstrPending: break; case TExprNode::EState::ConstrInProgress: return IGraphTransformer::TStatus::Async; case TExprNode::EState::ConstrComplete: case TExprNode::EState::ExecutionInProgress: case TExprNode::EState::ExecutionRequired: case TExprNode::EState::ExecutionPending: case TExprNode::EState::ExecutionComplete: return TStatus::Ok; case TExprNode::EState::Error: return TStatus::Error; default: YQL_ENSURE(false, "Unknown state"); } input->SetState(TExprNode::EState::ConstrPending); switch (input->Type()) { case TExprNode::Atom: case TExprNode::World: input->SetState(TExprNode::EState::ConstrComplete); CheckExpected(*input); return TStatus::Ok; case TExprNode::List: { retStatus = TransformChildren(input, output, ctx); if (retStatus == TStatus::Ok) { retStatus = CallableTransformer->Transform(input, output, ctx); if (retStatus == TStatus::Ok) { input->SetState(TExprNode::EState::ConstrComplete); CheckExpected(*input); break; } } if (retStatus != TStatus::Error && input != output) { processedPair.first->second = output; } break; } case TExprNode::Lambda: { YQL_ENSURE(input->ChildrenSize() > 0U); TExprNode::TPtr out; auto argStatus = TransformNode(input->HeadPtr(), out, ctx); UpdateStatusIfChanged(argStatus, input->HeadPtr(), out); if (argStatus.Level == TStatus::Error) { input->SetState(TExprNode::EState::Error); return argStatus; } if (argStatus.Level == TStatus::Repeat) return TStatus::Ok; TStatus bodyStatus = TStatus::Ok; TExprNode::TListType newBody; newBody.reserve(input->ChildrenSize() - 1U); bool updatedChildren = false; for (ui32 i = 1U; i < input->ChildrenSize(); ++i) { const auto child = input->ChildPtr(i); TExprNode::TPtr newChild; auto childStatus = TransformNode(child, newChild, ctx); UpdateStatusIfChanged(childStatus, child, newChild); updatedChildren = updatedChildren || (newChild != child); bodyStatus = bodyStatus.Combine(childStatus); newBody.emplace_back(std::move(newChild)); } retStatus = argStatus.Combine(bodyStatus); if (retStatus != TStatus::Ok) { if (retStatus.Level == TStatus::Error) { input->SetState(TExprNode::EState::Error); } else if (updatedChildren) { output = ctx.DeepCopyLambda(*input, std::move(newBody)); processedPair.first->second = output; HasRenames = true; } } else { if (input->ChildrenSize() != 2U) input->SetState(TExprNode::EState::ConstrComplete); else input->CopyConstraints(input->Tail()); CheckExpected(*input); } break; } case TExprNode::Argument: if (input->GetState() != TExprNode::EState::ConstrComplete) { return TStatus::Repeat; } return TStatus::Ok; case TExprNode::Arguments: { if (input->Children().empty()) { if (TExprNode::EState::ConstrComplete == input->GetState()) { return TStatus::Ok; } return TStatus::Repeat; } retStatus = TStatus::Ok; for (auto& child : input->Children()) { TExprNode::TPtr tmp; auto childStatus = TransformNode(child, tmp, ctx); UpdateStatusIfChanged(childStatus, child, tmp); YQL_ENSURE(tmp == child); retStatus = retStatus.Combine(childStatus); } if (retStatus != TStatus::Ok) { if (retStatus.Level == TStatus::Error) { input->SetState(TExprNode::EState::Error); } } else { input->SetState(TExprNode::EState::ConstrComplete); } return retStatus; } case TExprNode::Callable: { retStatus = TransformChildren(input, output, ctx); if (retStatus != TStatus::Ok) { if (retStatus != TStatus::Error && input != output) { processedPair.first->second = output; } break; } CurrentFunctions.top().second = true; retStatus = CallableTransformer->Transform(input, output, ctx); if (retStatus == TStatus::Error) { input->SetState(TExprNode::EState::Error); } else if (retStatus == TStatus::Ok) { // Sanity check for (size_t i = 0; i < input->ChildrenSize(); ++i) { YQL_ENSURE(input->Child(i)->GetState() >= TExprNode::EState::ConstrComplete, "Child with index " << i << " of callable " << TString{input->Content()}.Quote() << " has bad state after constraint transform"); } input->SetState(TExprNode::EState::ConstrComplete); CheckExpected(*input); } else if (retStatus == TStatus::Async) { CallableInputs.push_back(input); input->SetState(TExprNode::EState::ConstrInProgress); } else { if (output != input.Get()) { processedPair.first->second = output; HasRenames = true; } } break; } default: YQL_ENSURE(false, "Unknown type"); } if (retStatus.Level != TStatus::Repeat || retStatus.HasRestart) { return retStatus; } input = output; } } TStatus TransformChildren(const TExprNode::TPtr& input, TExprNode::TPtr& output, TExprContext& ctx) { TStatus combinedStatus = TStatus::Ok; TExprNode::TListType newChildren; bool updatedChildren = false; for (ui32 i = 0; i < input->ChildrenSize(); ++i) { const auto child = input->ChildPtr(i); TExprNode::TPtr newChild; auto childStatus = TransformNode(child, newChild, ctx); UpdateStatusIfChanged(childStatus, child, newChild); updatedChildren = updatedChildren || (newChild != child); combinedStatus = combinedStatus.Combine(childStatus); newChildren.emplace_back(std::move(newChild)); } if (combinedStatus != TStatus::Ok) { if (combinedStatus.Level == TStatus::Error) { input->SetState(TExprNode::EState::Error); } else if (updatedChildren) { output = ctx.ChangeChildren(*input, std::move(newChildren)); HasRenames = true; } } return combinedStatus; } void UpdateStatusIfChanged(TStatus& status, const TExprNode::TPtr& input, const TExprNode::TPtr& output) { if (status.Level == TStatus::Ok && input != output) { status = TStatus(TStatus::Repeat, status.HasRestart); } } void CheckExpected(const TExprNode& input) { if constexpr (DisableCheck) return; if (const auto it = Types.ExpectedConstraints.find(input.UniqueId()); it != Types.ExpectedConstraints.cend()) { for (const auto expectedConstr: it->second) { if (!Types.DisableConstraintCheck.contains(expectedConstr->GetName())) { if (auto newConstr = input.GetConstraint(expectedConstr->GetName())) { if (expectedConstr->GetName() == TMultiConstraintNode::Name()) { YQL_ENSURE(static_cast(newConstr)->FilteredIncludes(*expectedConstr, Types.DisableConstraintCheck), "Rewrite error, unequal " << *newConstr << " constraint in node " << input.Content() << ", previous was " << *expectedConstr); } else { YQL_ENSURE(newConstr->Includes(*expectedConstr), "Rewrite error, unequal " << *newConstr << " constraint in node " << input.Content() << ", previous was " << *expectedConstr); } } else { if (expectedConstr->GetName() == TMultiConstraintNode::Name()) { // Constraint Multi(0:{Empty},1:{Empty}, ..., N:{Empty}) can be reduced to Empty newConstr = input.GetConstraint(); } YQL_ENSURE(newConstr, "Rewrite error, missing " << *expectedConstr << " constraint in node " << input.Content()); } } } } } private: TAutoPtr CallableTransformer; std::deque CallableInputs; TNodeOnNodeOwnedMap Processed; bool HasRenames = false; std::stack> CurrentFunctions; TTypeAnnotationContext& Types; }; } // namespace TAutoPtr CreateConstraintTransformer(TTypeAnnotationContext& types, bool instantOnly, bool subGraph, bool disableCheck) { TAutoPtr callableTransformer(new TCallableConstraintTransformer(types, instantOnly, subGraph)); return disableCheck ? static_cast(new TConstraintTransformer(callableTransformer, types)): static_cast(new TConstraintTransformer(callableTransformer, types)); } TAutoPtr CreateDefCallableConstraintTransformer() { return new TDefaultCallableConstraintTransformer(); } IGraphTransformer::TStatus UpdateLambdaConstraints(const TExprNode& lambda) { const auto args = lambda.Child(0); for (const auto& arg: args->Children()) { if (arg->GetState() == TExprNode::EState::TypeComplete || arg->GetState() == TExprNode::EState::ConstrPending) { arg->SetState(TExprNode::EState::ConstrComplete); } YQL_ENSURE(arg->GetAllConstraints().empty()); } if (args->GetState() == TExprNode::EState::TypeComplete || args->GetState() == TExprNode::EState::ConstrPending) { args->SetState(TExprNode::EState::ConstrComplete); } if (lambda.GetState() != TExprNode::EState::ConstrComplete) { return IGraphTransformer::TStatus::Repeat; } return IGraphTransformer::TStatus::Ok; } IGraphTransformer::TStatus UpdateLambdaConstraints(TExprNode::TPtr& lambda, TExprContext& ctx, const TArrayRef& constraints) { bool updateArgs = false; const auto args = lambda->Child(0); YQL_ENSURE(args->ChildrenSize() == constraints.size()); size_t i = 0; for (const auto& constrList: constraints) { const auto arg = args->Child(i++); if (arg->GetState() == TExprNode::EState::TypeComplete || arg->GetState() == TExprNode::EState::ConstrPending) { for (const auto c: constrList) { arg->AddConstraint(c); } arg->SetState(TExprNode::EState::ConstrComplete); } else { if (constrList.size() != arg->GetAllConstraints().size() || !AllOf(constrList, [arg] (const TConstraintNode* c) { return arg->GetConstraint(c->GetName()) == c; })) { updateArgs = true; } } } if (updateArgs) { TNodeOnNodeOwnedMap replaces(constraints.size()); TExprNode::TListType argsChildren; argsChildren.reserve(constraints.size()); i = 0; for (const auto& constrList: constraints) { const auto arg = args->Child(i++); const auto newArg = ctx.ShallowCopy(*arg); newArg->SetTypeAnn(arg->GetTypeAnn()); for (const auto c: constrList) { newArg->AddConstraint(c); } newArg->SetState(TExprNode::EState::ConstrComplete); YQL_ENSURE(replaces.emplace(arg, newArg).second); argsChildren.emplace_back(std::move(newArg)); } auto newArgs = ctx.NewArguments(args->Pos(), std::move(argsChildren)); newArgs->SetTypeAnn(ctx.MakeType()); newArgs->SetState(TExprNode::EState::ConstrComplete); const auto type = lambda->GetTypeAnn(); lambda = ctx.NewLambda(lambda->Pos(), std::move(newArgs), ctx.ReplaceNodes(GetLambdaBody(*lambda), replaces)); lambda->SetTypeAnn(type); lambda->Head().ForEachChild(std::bind(&TExprNode::SetDependencyScope, std::placeholders::_1, lambda.Get(), lambda.Get())); return IGraphTransformer::TStatus::Repeat; } if (args->GetState() == TExprNode::EState::TypeComplete || args->GetState() == TExprNode::EState::ConstrPending) { args->SetState(TExprNode::EState::ConstrComplete); } if (lambda->GetState() != TExprNode::EState::ConstrComplete) { return IGraphTransformer::TStatus::Repeat; } return IGraphTransformer::TStatus::Ok; } IGraphTransformer::TStatus UpdateAllChildLambdasConstraints(const TExprNode& node) { IGraphTransformer::TStatus status = IGraphTransformer::TStatus::Ok; for (ui32 i = 0; i < node.ChildrenSize(); ++i) { const auto child = node.Child(i); if (child->Type() == TExprNode::EType::Lambda) { status = status.Combine(UpdateLambdaConstraints(*child)); } } return status; } } // namespace NYql