#include #include #include "mkql_computation_list_adapter.h" #include "mkql_computation_node_impl.h" #include "mkql_computation_node.h" #include "mkql_value_builder.h" #include #include #include #include "mkql_validate.h" #include #include #include #include #include #include namespace NYql { namespace { using namespace NKikimr::NMiniKQL; static const ui32 RAW_INDEX_NO_HOLE = -1; static const ui32 RAW_BROKEN_INDEX_LIST_TO_DICT = 1; template NUdf::TUnboxedValue ToUnboxedValue(const T& val) { return NUdf::TUnboxedValuePod(val); } NUdf::TUnboxedValue ToUnboxedValue(const TString& val) { return MakeString(val); } NUdf::TUnboxedValue ToUnboxedValue(const NUdf::IBoxedValuePtr& val) { return NUdf::TUnboxedValuePod(NUdf::IBoxedValuePtr(val)); } } // namespace NMiniKQL /// support for build Struct type @{ namespace NUdf { template struct TListRefIterator: public TBoxedValue { TListRefIterator(const TContainer& listRef, ui32 holePos) : ListRef(listRef) , Index(-1) , HolePos(holePos) {} private: const TContainer& ListRef; ui32 Index; ui32 HolePos; bool Next(NUdf::TUnboxedValue& value) final { if (++Index >= ListRef.size()) return false; value = Index == HolePos ? NUdf::TUnboxedValue(NUdf::TUnboxedValuePod(42)) : ToUnboxedValue(ListRef[Index]); return true; } }; template struct TListRef: public NUdf::TBoxedValue { TListRef(const TContainer& listRef, ui32 holePos = RAW_INDEX_NO_HOLE) : ListRef(listRef) , HolePos(holePos) {} private: const TContainer& ListRef; const NUdf::IValueBuilder* ValueBuilder; ui32 HolePos; bool HasFastListLength() const override { return true; } ui64 GetListLength() const override { return ListRef.size(); } ui64 GetEstimatedListLength() const override { return ListRef.size(); } NUdf::TUnboxedValue GetListIterator() const override { return NUdf::TUnboxedValuePod(new TListRefIterator(ListRef, HolePos)); } NUdf::IBoxedValuePtr ToIndexDictImpl(const IValueBuilder& builder) const override { return TNoDictIndex ? nullptr : builder.ToIndexDict(NUdf::TUnboxedValuePod( new TListRef(ListRef, TIndexDictBrokenHole))).AsBoxed(); } }; struct PersonStruct { static const size_t MEMBERS_COUNT = 3; static ui32 MetaIndexes[MEMBERS_COUNT]; static ui32 MetaBackIndexes[MEMBERS_COUNT]; TString FirstName; TString LastName; ui32 Age; NUdf::TUnboxedValue GetByIndex(ui32 index) const { switch (index) { case 0: return ToUnboxedValue(FirstName); case 1: return ToUnboxedValue(LastName); case 2: return NUdf::TUnboxedValuePod(Age); default: Y_ABORT("Unexpected"); } } }; ui32 PersonStruct::MetaIndexes[MEMBERS_COUNT]; ui32 PersonStruct::MetaBackIndexes[MEMBERS_COUNT]; struct PersonStructWithOptList { static const size_t MEMBERS_COUNT = 4; static ui32 MetaIndexes[MEMBERS_COUNT]; static ui32 MetaBackIndexes[MEMBERS_COUNT]; TString FirstName; TString LastName; ui32 Age; typedef std::vector TTagList; TTagList Tags; NUdf::TUnboxedValue GetByIndex(ui32 index) const { switch (index) { case 0: return ToUnboxedValue(FirstName); case 1: return ToUnboxedValue(LastName); case 2: return NUdf::TUnboxedValuePod(Age); case 3: return Tags.empty() ? NUdf::TUnboxedValuePod() : NUdf::TUnboxedValuePod(new TListRef(Tags)); default: Y_ABORT("Unexpected"); } } }; ui32 PersonStructWithOptList::MetaIndexes[MEMBERS_COUNT]; ui32 PersonStructWithOptList::MetaBackIndexes[MEMBERS_COUNT]; struct TCallableOneUi32Arg { }; namespace NImpl { template<> struct TTypeBuilderHelper { static TType* Build(const IFunctionTypeInfoBuilder& builder) { auto structBuilder = builder.Struct(3); structBuilder->AddField("FirstName", &PersonStruct::MetaIndexes[0]) .AddField("LastName", &PersonStruct::MetaIndexes[1]) .AddField("Age", &PersonStruct::MetaIndexes[2]); auto structType = structBuilder->Build(); for (const auto& index: PersonStruct::MetaIndexes) { Y_ABORT_UNLESS(index < NUdf::PersonStruct::MEMBERS_COUNT); NUdf::PersonStruct::MetaBackIndexes[index] = &index - PersonStruct::MetaIndexes; Y_ABORT_UNLESS(NUdf::PersonStruct::MetaBackIndexes[index] < NUdf::PersonStruct::MEMBERS_COUNT); } return structType; } }; template<> struct TTypeBuilderHelper { static TType* Build(const IFunctionTypeInfoBuilder& builder) { auto listTags = builder.List()->Item().Build(); auto optionalListTags = builder.Optional()->Item(listTags).Build(); auto structBuilder = builder.Struct(3); structBuilder->AddField("FirstName", &PersonStructWithOptList::MetaIndexes[0]) .AddField("LastName", &PersonStructWithOptList::MetaIndexes[1]) .AddField("Age", &PersonStructWithOptList::MetaIndexes[2]) .AddField("Tags", optionalListTags, &PersonStructWithOptList::MetaIndexes[3]); auto structType = structBuilder->Build(); for (const auto& index: PersonStructWithOptList::MetaIndexes) { Y_ABORT_UNLESS(index < NUdf::PersonStructWithOptList::MEMBERS_COUNT); NUdf::PersonStructWithOptList::MetaBackIndexes[index] = &index - PersonStructWithOptList::MetaIndexes; Y_ABORT_UNLESS(NUdf::PersonStructWithOptList::MetaBackIndexes[index] < NUdf::PersonStructWithOptList::MEMBERS_COUNT); } return structType; } }; template<> struct TTypeBuilderHelper { static TType* Build(const IFunctionTypeInfoBuilder& builder) { auto callableBuilder = builder.Callable(1); callableBuilder->Returns(); callableBuilder->Arg(); return callableBuilder->Build(); } }; } // namespace NImpl } // namespace NUdf /// @} struct TBrokenSeqListIterator: public NUdf::TBoxedValue { TBrokenSeqListIterator(ui32 size, ui32 holePos) : Size(size) , HolePos(holePos) , Index(-1) {} private: ui32 Size; ui32 HolePos; ui32 Index; bool Skip() final { return ++Index < Size; } bool Next(NUdf::TUnboxedValue& value) final { if (!Skip()) return false; value = Index == HolePos ? NUdf::TUnboxedValuePod() : NUdf::TUnboxedValuePod(Index); return true; } }; struct TBrokenSeqListBoxedValue: public NUdf::TBoxedValue { TBrokenSeqListBoxedValue(ui32 size, ui32 holePos) : ListSize(size) , HolePos(holePos) {} private: ui32 ListSize; ui32 HolePos; bool HasFastListLength() const override { return true; } ui64 GetListLength() const override { return ListSize; } ui64 GetEstimatedListLength() const override { return ListSize; } NUdf::TUnboxedValue GetListIterator() const override { return NUdf::TUnboxedValuePod(new TBrokenSeqListIterator(ListSize, HolePos)); } }; template struct TBrokenStructBoxedValue: public NUdf::TBoxedValue { TBrokenStructBoxedValue(const TStructType& data, ui32 holePos = RAW_INDEX_NO_HOLE) : Struct(data) , HolePos(holePos) {} private: const TStructType& Struct; ui32 HolePos; NUdf::TUnboxedValue GetElement(ui32 index) const override { if (index == HolePos) { return NUdf::TUnboxedValuePod(); } return Struct.GetByIndex(TStructType::MetaBackIndexes[index]); } }; namespace { template<> NUdf::TUnboxedValue ToUnboxedValue(const NUdf::PersonStruct& val) { return NUdf::TUnboxedValuePod(new TBrokenStructBoxedValue(val)); } template<> NUdf::TUnboxedValue ToUnboxedValue(const NUdf::PersonStructWithOptList& val) { return NUdf::TUnboxedValuePod(new TBrokenStructBoxedValue(val)); } template struct TBrokenTupleBoxedValue: public NUdf::TBoxedValue { TBrokenTupleBoxedValue(const TTupleType& tuple, ui32 holePos) : Tuple(tuple) , HolePos(holePos) {} private: const TTupleType& Tuple; ui32 HolePos; NUdf::TUnboxedValue GetElement(ui32 index) const override { if (index == HolePos) { return NUdf::TUnboxedValuePod(); } switch (index) { case 0: return ToUnboxedValue(std::get<0>(Tuple)); case 1: return ToUnboxedValue(std::get<1>(Tuple)); case 2: return ToUnboxedValue(std::get<2>(Tuple)); case 3: return ToUnboxedValue(std::get<3>(Tuple)); default: Y_ABORT("Unexpected"); } } }; typedef std::pair PosPair; template struct TBrokenDictIterator: public NUdf::TBoxedValue { TBrokenDictIterator(const std::vector>& dictData, PosPair holePos) : DictData(dictData) , HolePos(holePos) , Index(-1) {} private: const std::vector>& DictData; PosPair HolePos; ui32 Index; bool Skip() final { return ++Index < DictData.size(); } bool Next(NUdf::TUnboxedValue& key) final { if (!Skip()) return false; key = Index == HolePos.first ? NUdf::TUnboxedValuePod() : NUdf::TUnboxedValuePod(DictData[Index].first); return true; } bool NextPair(NUdf::TUnboxedValue& key, NUdf::TUnboxedValue& payload) final { if (!Next(key)) return false; payload = Index == HolePos.second ? NUdf::TUnboxedValue() : ToUnboxedValue(DictData[Index].second); return true; } }; template struct TBrokenDictBoxedValue: public NUdf::TBoxedValue { TBrokenDictBoxedValue(const std::vector>& dictData, PosPair holePos, NUdf::TUnboxedValue&& hole = NUdf::TUnboxedValuePod()) : DictData(dictData) , HolePos(holePos) , Hole(std::move(hole)) {} private: const std::vector> DictData; PosPair HolePos; NUdf::TUnboxedValue Hole; NUdf::TUnboxedValue GetKeysIterator() const override { return NUdf::TUnboxedValuePod(new TBrokenDictIterator(DictData, HolePos)); } NUdf::TUnboxedValue GetDictIterator() const override { return NUdf::TUnboxedValuePod(new TBrokenDictIterator(DictData, HolePos)); } }; struct TThrowerValue: public NUdf::TBoxedValue { static long Count; TThrowerValue(NUdf::IBoxedValuePtr&& owner = NUdf::IBoxedValuePtr()) : Owner(std::move(owner)) { ++Count; } ~TThrowerValue() { --Count; } private: const NUdf::IBoxedValuePtr Owner; bool Skip() override { ythrow yexception() << "Throw"; } NUdf::TUnboxedValue GetListIterator() const override { return NUdf::TUnboxedValuePod(new TThrowerValue(const_cast(this))); } }; SIMPLE_UDF(TException, NUdf::TListType()) { Y_UNUSED(valueBuilder); Y_UNUSED(args); return NUdf::TUnboxedValuePod(new TThrowerValue); } long TThrowerValue::Count = 0L; SIMPLE_UDF(TVoid, void()) { Y_UNUSED(valueBuilder); Y_UNUSED(args); return NUdf::TUnboxedValuePod::Void(); } SIMPLE_UDF_RUN(TNonEmpty, ui32(), NUdf::TOptional) { Y_UNUSED(valueBuilder); Y_UNUSED(args); return NUdf::TUnboxedValuePod(42); } SIMPLE_UDF(TOptionalNonEmpty, NUdf::TOptional()) { Y_UNUSED(valueBuilder); Y_UNUSED(args); return NUdf::TUnboxedValuePod(42); } SIMPLE_UDF_RUN(TOptionalEmpty, NUdf::TOptional(), NUdf::TOptional) { Y_UNUSED(valueBuilder); Y_UNUSED(args); return NUdf::TUnboxedValuePod(); } SIMPLE_UDF(TSub2Mul2BrokenOnLess2, ui32(ui32)) { Y_UNUSED(valueBuilder); const ui32 arg = args[0].Get(); if (arg >= 2) { return NUdf::TUnboxedValuePod((arg - 2) * 2); } return NUdf::TUnboxedValuePod(); } SIMPLE_UDF_RUN(TBackSub2Mul2, ui32(NUdf::TCallableOneUi32Arg, ui32), NUdf::TOptional) { const auto func = args[0]; const auto& arg = args[1]; auto usedArg = NUdf::TUnboxedValuePod(); if (arg.Get() < 100) { usedArg = arg; } auto funcResult = func.Run(valueBuilder, &usedArg); const auto& backResult = funcResult.Get() / 2 + 2; return NUdf::TUnboxedValuePod(backResult); } SIMPLE_UDF(TSeqList, NUdf::TListType(ui32)) { const ui32 size = args[0].Get(); std::vector res; res.resize(size); for (ui32 i = 0; i < size; ++i) { res[i] = NUdf::TUnboxedValuePod(i); } return valueBuilder->NewList(res.data(), res.size()); } SIMPLE_UDF_RUN(TSeqListWithHole, NUdf::TListType(ui32, ui32), NUdf::TOptional) { Y_UNUSED(valueBuilder); const ui32 size = args[0].Get(); const ui32 hole = args[1].Get(); NUdf::IBoxedValuePtr boxed(new TBrokenSeqListBoxedValue(size, hole)); return NUdf::TUnboxedValuePod(std::move(boxed)); } static const auto TUPLE = std::make_tuple(ui8(33), TString("world"), ui64(0xFEEDB00B2A115E), TString("funny bunny")); typedef NUdf::TTuple NUdfTuple; SIMPLE_UDF(TTuple, NUdfTuple(ui32)) { Y_UNUSED(valueBuilder); const ui32 holePos = args[0].Get(); NUdf::IBoxedValuePtr boxed(new TBrokenTupleBoxedValue(TUPLE, holePos)); return NUdf::TUnboxedValuePod(std::move(boxed)); } static const std::vector> DICT_DIGIT2DIGIT = { {1, 100500}, {42, 0xDEADBEAF}, {911, 1234567890}, {777, 777777777777}, }; typedef NUdf::TDict NUdfDictDigDig; SIMPLE_UDF_RUN(TDictDigDig, NUdfDictDigDig(ui32, ui32), NUdf::TOptional) { Y_UNUSED(valueBuilder); const ui32 holeKey = args[0].Get(); const ui32 holeValue = args[1].Get(); NUdf::IBoxedValuePtr boxed(new TBrokenDictBoxedValue( DICT_DIGIT2DIGIT, std::make_pair(holeKey, holeValue))); return NUdf::TUnboxedValuePod(std::move(boxed)); } SIMPLE_UDF(TDictDigDigHoleAsOpt, NUdfDictDigDig(ui32, ui32)) { Y_UNUSED(valueBuilder); const ui32 holeKey = args[0].Get(); const ui32 holeValue = args[1].Get(); NUdf::IBoxedValuePtr boxed(new TBrokenDictBoxedValue(DICT_DIGIT2DIGIT, std::make_pair(holeKey, holeValue), NUdf::TUnboxedValuePod())); return NUdf::TUnboxedValuePod(std::move(boxed)); } static const NUdf::PersonStruct STRUCT_PERSON_JONNIE = {"Johnnie Walker", "Blue Label", 25}; static const NUdf::PersonStruct STRUCT_PERSON_HITHCOCK = {"Alfred", "Hithcock", 81}; static const NUdf::PersonStruct STRUCT_PERSON_LOVECRAFT = {"Howard", "Lovecraft", 25}; static const NUdf::PersonStruct STRUCT_PERSON_KING = {"Stephen", "King", 25}; static const NUdf::PersonStructWithOptList STRUCT_PERSON_HITHCOCK_LIST = {"Alfred", "Hithcock", 81, {}}; static const NUdf::PersonStructWithOptList STRUCT_PERSON_LOVECRAFT_LIST = {"Howard", "Lovecraft", 25, {3, 2, 99}}; static const NUdf::PersonStructWithOptList STRUCT_PERSON_KING_LIST = {"Stephen", "King", 25, {}}; SIMPLE_UDF_RUN(TPersonStruct, NUdf::PersonStruct(ui32), NUdf::TOptional) { Y_UNUSED(valueBuilder); const ui32 holePos = args[0].Get(); NUdf::IBoxedValuePtr boxed(new TBrokenStructBoxedValue(STRUCT_PERSON_JONNIE, holePos)); return NUdf::TUnboxedValuePod(std::move(boxed)); } typedef NUdf::TTuple NUdfPersonTuple; static const auto TUPLE_OF_PERSON = std::make_tuple( STRUCT_PERSON_HITHCOCK_LIST, STRUCT_PERSON_JONNIE, STRUCT_PERSON_LOVECRAFT_LIST, STRUCT_PERSON_KING); static const auto TUPLE_OF_PERSON_NO_LIST = std::make_tuple(STRUCT_PERSON_HITHCOCK_LIST, STRUCT_PERSON_JONNIE, STRUCT_PERSON_KING_LIST, STRUCT_PERSON_KING); SIMPLE_UDF(TTupleOfPersonStruct, NUdfPersonTuple(ui32)) { Y_UNUSED(valueBuilder); const ui32 holePos = args[0].Get(); NUdf::IBoxedValuePtr boxed(new TBrokenTupleBoxedValue(TUPLE_OF_PERSON, holePos)); return NUdf::TUnboxedValuePod(std::move(boxed)); } SIMPLE_UDF(TTupleOfPersonStructNoList, NUdfPersonTuple(ui32)) { Y_UNUSED(valueBuilder); const ui32 holePos = args[0].Get(); NUdf::IBoxedValuePtr boxed(new TBrokenTupleBoxedValue(TUPLE_OF_PERSON_NO_LIST, holePos)); return NUdf::TUnboxedValuePod(std::move(boxed)); } static const std::vector LIST_OF_STRUCT_PERSON = { STRUCT_PERSON_HITHCOCK_LIST, STRUCT_PERSON_LOVECRAFT_LIST, STRUCT_PERSON_KING_LIST }; typedef NUdf::TDict TIndexDictFromPersonList; SIMPLE_UDF(TListOfPersonStructToIndexDict, TIndexDictFromPersonList(ui32)) { Y_UNUSED(valueBuilder); Y_UNUSED(args); NUdf::IBoxedValuePtr boxed(new NUdf::TListRef(LIST_OF_STRUCT_PERSON)); return valueBuilder->ToIndexDict(NUdf::TUnboxedValuePod(std::move(boxed))); } SIMPLE_UDF(TListOfPersonStruct, NUdf::TListType(ui32)) { Y_UNUSED(valueBuilder); Y_UNUSED(args); NUdf::IBoxedValuePtr boxed(new NUdf::TListRef(LIST_OF_STRUCT_PERSON)); return NUdf::TUnboxedValuePod(std::move(boxed)); } SIMPLE_UDF(TListOfPersonStructWithBrokenIndexToDict, NUdf::TListType()) { Y_UNUSED(valueBuilder); Y_UNUSED(args); NUdf::IBoxedValuePtr boxed(new NUdf::TListRef( LIST_OF_STRUCT_PERSON)); return NUdf::TUnboxedValuePod(std::move(boxed)); } static const NUdf::PersonStruct* DICT_DIGIT2PERSON_BROKEN_CONTENT_BY_INDEX[] = { &STRUCT_PERSON_HITHCOCK, &STRUCT_PERSON_JONNIE, &STRUCT_PERSON_LOVECRAFT }; const ui32 DICT_DIGIT2PERSON_BROKEN_PERSON_INDEX = 1; const ui32 DICT_DIGIT2PERSON_BROKEN_STRUCT_INDEX = 2; const std::vector> MAKE_DICT_DIGIT2PERSON_BROKEN() { std::vector> DICT_DIGIT2PERSON_BROKEN = { { 333, new TBrokenStructBoxedValue(STRUCT_PERSON_HITHCOCK, RAW_INDEX_NO_HOLE) }, { 5, new TBrokenStructBoxedValue(STRUCT_PERSON_JONNIE, DICT_DIGIT2PERSON_BROKEN_STRUCT_INDEX) }, { 77, new TBrokenStructBoxedValue(STRUCT_PERSON_LOVECRAFT, RAW_INDEX_NO_HOLE) }, }; return DICT_DIGIT2PERSON_BROKEN; } typedef NUdf::TDict NUdfDictDigPerson; std::vector> MAKE_DICT_DIGIT2PERSON() { const std::vector> DICT_DIGIT2PERSON = { { 333, new TBrokenStructBoxedValue(STRUCT_PERSON_HITHCOCK, RAW_INDEX_NO_HOLE) }, { 5, new TBrokenStructBoxedValue(STRUCT_PERSON_JONNIE, RAW_INDEX_NO_HOLE) }, { 77, new TBrokenStructBoxedValue(STRUCT_PERSON_LOVECRAFT, RAW_INDEX_NO_HOLE) }, }; return DICT_DIGIT2PERSON; } SIMPLE_UDF(TDictOfPerson, NUdfDictDigPerson()) { Y_UNUSED(args); Y_UNUSED(valueBuilder); NUdf::IBoxedValuePtr boxed(new TBrokenDictBoxedValue( MAKE_DICT_DIGIT2PERSON(), std::make_pair(RAW_INDEX_NO_HOLE, RAW_INDEX_NO_HOLE))); return NUdf::TUnboxedValuePod(std::move(boxed)); } SIMPLE_UDF_RUN(TDictOfPersonBroken, NUdfDictDigPerson(), NUdf::TOptional) { Y_UNUSED(args); Y_UNUSED(valueBuilder); NUdf::IBoxedValuePtr boxed(new TBrokenDictBoxedValue( MAKE_DICT_DIGIT2PERSON_BROKEN(), std::make_pair(RAW_INDEX_NO_HOLE, RAW_INDEX_NO_HOLE))); return NUdf::TUnboxedValuePod(std::move(boxed)); } SIMPLE_MODULE(TUtUDF, TException, TVoid, TNonEmpty, TOptionalNonEmpty, TOptionalEmpty, TSub2Mul2BrokenOnLess2, TBackSub2Mul2, TSeqList, TSeqListWithHole, TTuple, TDictDigDig, TDictDigDigHoleAsOpt, TPersonStruct, TTupleOfPersonStruct, TTupleOfPersonStructNoList, TListOfPersonStructToIndexDict, TListOfPersonStruct, TListOfPersonStructWithBrokenIndexToDict, TDictOfPerson, TDictOfPersonBroken ) } // unnamed namespace TIntrusivePtr CreateFunctionRegistryWithUDFs() { auto freg = CreateFunctionRegistry(CreateBuiltinRegistry())->Clone(); freg->AddModule("", "UtUDF", new TUtUDF()); return freg; } Y_UNIT_TEST_SUITE(TMiniKQLValidateTest) { typedef std::function(TProgramBuilder&)> BuildArgsFunc; typedef std::function ValidateValueFunc; typedef std::function FullValidateValueFunc; void ProcessSimpleUdfFunc(const char* udfFuncName, BuildArgsFunc argsFunc = BuildArgsFunc(), ValidateValueFunc validateFunc = ValidateValueFunc(), FullValidateValueFunc fullValidateFunc = FullValidateValueFunc(), NUdf::EValidateMode validateMode = NUdf::EValidateMode::Lazy) { TScopedAlloc alloc(__LOCATION__); TTypeEnvironment env(alloc); NUdf::ITypeInfoHelper::TPtr typeInfoHelper(new TTypeInfoHelper); auto functionRegistry = CreateFunctionRegistryWithUDFs(); auto randomProvider = CreateDeterministicRandomProvider(1); auto timeProvider = CreateDeterministicTimeProvider(10000000); TProgramBuilder pgmBuilder(env, *functionRegistry); auto funcName = pgmBuilder.Udf(udfFuncName); std::vector execArgs; if (argsFunc) { execArgs = argsFunc(pgmBuilder); } auto pgmReturn = pgmBuilder.Apply(funcName, execArgs); TExploringNodeVisitor explorer; explorer.Walk(pgmReturn.GetNode(), env); TComputationPatternOpts opts(alloc.Ref(), env, GetBuiltinFactory(), functionRegistry.Get(), validateMode, NUdf::EValidatePolicy::Exception, "OFF", EGraphPerProcess::Multi); auto pattern = MakeComputationPattern(explorer, pgmReturn, {}, opts); auto graph = pattern->Clone(opts.ToComputationOptions(*randomProvider, *timeProvider)); const auto builder = static_cast(graph->GetTerminator()); builder->RethrowAtTerminate(); const TBindTerminator bind(graph->GetTerminator()); auto value = graph->GetValue(); if (validateFunc) { validateFunc(value, builder); } if (fullValidateFunc) { ui32 flags = 0; TFunctionTypeInfo funcInfo; TType* userType = nullptr; TStringBuf typeConfig; TStatus status = functionRegistry->FindFunctionTypeInfo(env, typeInfoHelper, nullptr, udfFuncName, userType, typeConfig, flags, {}, nullptr, &funcInfo); MKQL_ENSURE(status.IsOk(), status.GetError()); auto type = funcInfo.FunctionType->GetReturnType(); fullValidateFunc(value, builder, type); } } Y_UNIT_TEST(TestUdfException) { ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { valueBuilder->NewStringNotFilled(0xBAD).AsStringValue().Ref(); // Leak string. NUdf::TBoxedValueAccessor::Skip(*value.GetListIterator().AsBoxed().Release()); // Leak value and throw exception. }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.Exception", {}, validateFunc), yexception); UNIT_ASSERT_VALUES_EQUAL(TThrowerValue::Count, 0L); } Y_UNIT_TEST(TestUdfResultCheckVoid) { ProcessSimpleUdfFunc("UtUDF.Void"); } Y_UNIT_TEST(TestUdfResultCheckExceptionOnEmpty) { TScopedAlloc alloc(__LOCATION__); TTypeEnvironment env(alloc); bool wrapped = false; UNIT_ASSERT_EXCEPTION(TValidate::Value(nullptr, env.GetTypeOfTypeLazy(), NUdf::TUnboxedValuePod(), "ut for verify empty value exception", &wrapped), TUdfValidateException); UNIT_ASSERT(!wrapped); } Y_UNIT_TEST(TestUdfResultCheckNonEmpty) { ProcessSimpleUdfFunc("UtUDF.NonEmpty"); } Y_UNIT_TEST(TestUdfResultCheckOptionalNonEmpty) { ProcessSimpleUdfFunc("UtUDF.OptionalNonEmpty"); } Y_UNIT_TEST(TestUdfResultCheckOptionalEmpty) { ProcessSimpleUdfFunc("UtUDF.OptionalEmpty"); } std::vector MakeCallableInArgs(ui32 testVal, TProgramBuilder& pgmBuilder) { const auto& functionRegistry = pgmBuilder.GetFunctionRegistry(); const auto udfFuncName = "UtUDF.Sub2Mul2BrokenOnLess2"; ui32 flags = 0; TFunctionTypeInfo funcInfo; TType* userType = nullptr; TStringBuf typeConfig; NUdf::ITypeInfoHelper::TPtr typeInfoHelper(new TTypeInfoHelper); TStatus status = functionRegistry.FindFunctionTypeInfo(pgmBuilder.GetTypeEnvironment(), typeInfoHelper, nullptr, udfFuncName, userType, typeConfig, flags, {}, nullptr, &funcInfo); MKQL_ENSURE(status.IsOk(), status.GetError()); auto callable = pgmBuilder.Udf(udfFuncName); return std::vector{callable, pgmBuilder.NewDataLiteral(testVal)}; }; Y_UNIT_TEST(TestVerifyArgsCallableCorrect) { ui32 testVal = 44; BuildArgsFunc argsFunc = [testVal](TProgramBuilder& pgmBuilder) { return MakeCallableInArgs(testVal, pgmBuilder); }; ValidateValueFunc validateFunc = [testVal](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); UNIT_ASSERT_VALUES_EQUAL(testVal, value.Get()); }; ProcessSimpleUdfFunc("UtUDF.BackSub2Mul2", argsFunc, validateFunc); } Y_UNIT_TEST(TestVerifyArgsCallableBrokenOnArgument) { ui32 testVal = 101; BuildArgsFunc argsFunc = [testVal](TProgramBuilder& pgmBuilder) { return MakeCallableInArgs(testVal, pgmBuilder); }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.BackSub2Mul2", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestVerifyArgsCallableBrokenOnReturn) { ui32 testVal = 1; BuildArgsFunc argsFunc = [testVal](TProgramBuilder& pgmBuilder) { return MakeCallableInArgs(testVal, pgmBuilder); }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.BackSub2Mul2", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckEmptySeqList) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(0)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); auto listIter = value.GetListIterator(); UNIT_ASSERT(!listIter.Skip()); }; ProcessSimpleUdfFunc("UtUDF.SeqList", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckSeqList) { static constexpr ui32 listSize = 31; BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(listSize)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); ui32 index = 0; auto listIter = value.GetListIterator(); for (NUdf::TUnboxedValue item; listIter.Next(item); ++index) { UNIT_ASSERT_VALUES_EQUAL(item.Get(), index); } UNIT_ASSERT_VALUES_EQUAL(index, listSize); }; ProcessSimpleUdfFunc("UtUDF.SeqList", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckSeqListWithHoleFirst) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { const ui32 listSize = 31; return std::vector{pgmBuilder.NewDataLiteral(listSize), pgmBuilder.NewDataLiteral(0)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); auto listIter = value.GetListIterator(); NUdf::TUnboxedValue item; UNIT_ASSERT_EXCEPTION(listIter.Next(item), TUdfValidateException); for (ui32 index = 1; listIter.Next(item); ++index) { UNIT_ASSERT_VALUES_EQUAL(item.Get(), index); } }; ProcessSimpleUdfFunc("UtUDF.SeqListWithHole", argsFunc, validateFunc); UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.SeqListWithHole", argsFunc, validateFunc, {}, NUdf::EValidateMode::Greedy), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckSeqListWithHoleMiddle) { static constexpr ui32 listSize = 31; BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(listSize), pgmBuilder.NewDataLiteral(listSize / 2)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); UNIT_ASSERT_VALUES_EQUAL(value.GetListLength(), listSize); ui32 index = 0; const auto listIter = value.GetListIterator(); for (NUdf::TUnboxedValue item; index < listSize / 2 && listIter.Next(item); ++index) { UNIT_ASSERT_VALUES_EQUAL(item.Get(), index); } NUdf::TUnboxedValue bad; UNIT_ASSERT_EXCEPTION(listIter.Next(bad), TUdfValidateException); ++index; for (NUdf::TUnboxedValue item; listIter.Next(item); ++index) { UNIT_ASSERT_VALUES_EQUAL(item.Get(), index); } }; ProcessSimpleUdfFunc("UtUDF.SeqListWithHole", argsFunc, validateFunc); UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.SeqListWithHole", argsFunc, validateFunc, {}, NUdf::EValidateMode::Greedy), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckSeqListWithHoleLast) { static constexpr ui32 listSize = 31; BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(listSize), pgmBuilder.NewDataLiteral(listSize - 1)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); UNIT_ASSERT_VALUES_EQUAL(value.GetListLength(), listSize); ui32 index = 0; auto listIter = value.GetListIterator(); for (NUdf::TUnboxedValue item; index < listSize - 1 && listIter.Next(item); ++index) { UNIT_ASSERT_VALUES_EQUAL(item.Get(), index); } UNIT_ASSERT_VALUES_EQUAL(index, listSize - 1); NUdf::TUnboxedValue bad; UNIT_ASSERT_EXCEPTION(listIter.Next(bad), TUdfValidateException); }; ProcessSimpleUdfFunc("UtUDF.SeqListWithHole", argsFunc, validateFunc); UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.SeqListWithHole", argsFunc, validateFunc, {}, NUdf::EValidateMode::Greedy), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckTuple) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; ProcessSimpleUdfFunc("UtUDF.Tuple", argsFunc); } Y_UNIT_TEST(TestUdfResultCheckTupleWithHoleFirst) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(0)}; }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.Tuple", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckTupleWithHoleMiddle) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(std::tuple_size::value / 2)}; }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.Tuple", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckTupleWithHoleLast) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(std::tuple_size::value - 1)}; }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.Tuple", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckDictDigitDigitFull) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE), pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); auto dictIter = value.GetDictIterator(); ui32 index = 0; for (NUdf::TUnboxedValue key, payload; dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), DICT_DIGIT2DIGIT[index].first); UNIT_ASSERT_VALUES_EQUAL(payload.Get(), DICT_DIGIT2DIGIT[index].second); } UNIT_ASSERT_VALUES_EQUAL(index, DICT_DIGIT2DIGIT.size()); }; ProcessSimpleUdfFunc("UtUDF.DictDigDig", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckDictDigitDigitKeyHole) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(0), pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); auto dictIter = value.GetDictIterator(); NUdf::TUnboxedValue key, payload; UNIT_ASSERT_EXCEPTION(dictIter.NextPair(key, payload), TUdfValidateException); for (ui32 index = 1; dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), DICT_DIGIT2DIGIT[index].first); UNIT_ASSERT_VALUES_EQUAL(payload.Get(), DICT_DIGIT2DIGIT[index].second); } }; ProcessSimpleUdfFunc("UtUDF.DictDigDig", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckDictDigitDigitValueHole) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE), pgmBuilder.NewDataLiteral(DICT_DIGIT2DIGIT.size() - 1)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); auto dictIter = value.GetDictIterator(); NUdf::TUnboxedValue key, payload; for (ui32 index = 0; index < DICT_DIGIT2DIGIT.size() - 1 && dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), DICT_DIGIT2DIGIT[index].first); UNIT_ASSERT_VALUES_EQUAL(payload.Get(), DICT_DIGIT2DIGIT[index].second); } UNIT_ASSERT_EXCEPTION(dictIter.NextPair(key, payload), TUdfValidateException); }; ProcessSimpleUdfFunc("UtUDF.DictDigDig", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckDictDigitDigitHoleAsOptKeyHole) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE), pgmBuilder.NewDataLiteral(0)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); auto dictIter = value.GetDictIterator(); NUdf::TUnboxedValue key, payload; UNIT_ASSERT_EXCEPTION(dictIter.NextPair(key, payload), TUdfValidateException); for (ui32 index = 1; dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), DICT_DIGIT2DIGIT[index].first); UNIT_ASSERT_VALUES_EQUAL(payload.Get(), DICT_DIGIT2DIGIT[index].second); } }; ProcessSimpleUdfFunc("UtUDF.DictDigDig", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckDictDigitDigitHoleAsOptValueHole) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(DICT_DIGIT2DIGIT.size() - 1), pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); auto dictIter = value.GetDictIterator(); NUdf::TUnboxedValue key, payload; for (ui32 index = 0; index < DICT_DIGIT2DIGIT.size() - 1 && dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), DICT_DIGIT2DIGIT[index].first); UNIT_ASSERT_VALUES_EQUAL(payload.Get(), DICT_DIGIT2DIGIT[index].second); } UNIT_ASSERT_EXCEPTION(dictIter.NextPair(key, payload), TUdfValidateException); }; ProcessSimpleUdfFunc("UtUDF.DictDigDig", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckPersonStruct) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; ProcessSimpleUdfFunc("UtUDF.PersonStruct", argsFunc); } Y_UNIT_TEST(TestUdfResultCheckPersonStructWithHoleFirst) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(0)}; }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.PersonStruct", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckPersonStructWithHoleMiddle) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(1)}; }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.PersonStruct", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckPersonStructWithHoleLast) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(2)}; }; UNIT_ASSERT_EXCEPTION(ProcessSimpleUdfFunc("UtUDF.PersonStruct", argsFunc), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckTupleOfPersonStruct) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; FullValidateValueFunc fullValidateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder, const TType* type) { bool wrapped = false; TValidate>::Value(valueBuilder, type, NUdf::TUnboxedValuePod(value), "full verify func", &wrapped); UNIT_ASSERT(!wrapped); TValidate>::Value(valueBuilder, type, NUdf::TUnboxedValuePod(value), "full verify func", &wrapped); UNIT_ASSERT(wrapped); }; ProcessSimpleUdfFunc("UtUDF.TupleOfPersonStruct", argsFunc, {}, fullValidateFunc); ProcessSimpleUdfFunc("UtUDF.TupleOfPersonStruct", argsFunc, {}, fullValidateFunc, NUdf::EValidateMode::Greedy); } Y_UNIT_TEST(TestUdfResultCheckTupleOfPersonStructNoList) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; FullValidateValueFunc fullValidateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder, const TType* type) { bool wrapped = false; TValidate::Value(valueBuilder, type, NUdf::TUnboxedValuePod(value), "full verify func", &wrapped); UNIT_ASSERT(!wrapped); }; ProcessSimpleUdfFunc("UtUDF.TupleOfPersonStructNoList", argsFunc, {}, fullValidateFunc); ProcessSimpleUdfFunc("UtUDF.TupleOfPersonStructNoList", argsFunc, {}, fullValidateFunc, NUdf::EValidateMode::Greedy); } void ValidateDictOfPersonStructFunc(const NUdf::TUnboxedValuePod& value, ui32 lookupIndex = 2, ui32 broken_index = RAW_INDEX_NO_HOLE) { const auto person = value.Lookup(NUdf::TUnboxedValuePod(ui64(lookupIndex))); UNIT_ASSERT(person); auto firstName = person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[0]); UNIT_ASSERT_VALUES_EQUAL(TString(firstName.AsStringRef()), LIST_OF_STRUCT_PERSON[lookupIndex].FirstName); auto lastName = person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[1]); UNIT_ASSERT_VALUES_EQUAL(TString(lastName.AsStringRef()), LIST_OF_STRUCT_PERSON[lookupIndex].LastName); UNIT_ASSERT_VALUES_EQUAL(person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[2]).Get(), LIST_OF_STRUCT_PERSON[lookupIndex].Age); UNIT_ASSERT(!person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[3])); auto dictIter = value.GetDictIterator(); NUdf::TUnboxedValue key, payload; for (ui32 index = 0; index < broken_index && dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), index); auto person = payload; auto firstName = person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[0]); UNIT_ASSERT_VALUES_EQUAL(TString(firstName.AsStringRef()), LIST_OF_STRUCT_PERSON[index].FirstName); auto lastName = person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[1]); UNIT_ASSERT_VALUES_EQUAL(TString(lastName.AsStringRef()), LIST_OF_STRUCT_PERSON[index].LastName); UNIT_ASSERT_VALUES_EQUAL(person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[2]).Get(), LIST_OF_STRUCT_PERSON[index].Age); const auto origListPtr = LIST_OF_STRUCT_PERSON[index].Tags; if (origListPtr.empty()) { UNIT_ASSERT(!person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[3])); } else { auto memberTags = person.GetElement(NUdf::PersonStructWithOptList::MetaIndexes[3]); UNIT_ASSERT(memberTags); UNIT_ASSERT_VALUES_EQUAL(memberTags.GetListLength(), origListPtr.size()); auto origIter = origListPtr.begin(); auto iter = memberTags.GetListIterator(); for (NUdf::TUnboxedValue item; iter.Next(item); ++origIter) { UNIT_ASSERT_VALUES_EQUAL(item.Get(), *origIter); } } } if (broken_index < RAW_INDEX_NO_HOLE) UNIT_ASSERT_EXCEPTION(dictIter.NextPair(key, payload), TUdfValidateException); } Y_UNIT_TEST(TestUdfResultCheckListOfPersonStructToIndexDict) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder) { Y_UNUSED(valueBuilder); ValidateDictOfPersonStructFunc(value); }; ProcessSimpleUdfFunc("UtUDF.ListOfPersonStructToIndexDict", argsFunc, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckListOfPersonStruct) { BuildArgsFunc argsFunc = [](TProgramBuilder& pgmBuilder) { return std::vector{pgmBuilder.NewDataLiteral(RAW_INDEX_NO_HOLE)}; }; FullValidateValueFunc fullValidateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder, const TType* type) { Y_UNUSED(type); auto indexDict = valueBuilder->ToIndexDict(value); ValidateDictOfPersonStructFunc(indexDict); }; ProcessSimpleUdfFunc("UtUDF.ListOfPersonStruct", argsFunc, {}, fullValidateFunc); } Y_UNIT_TEST(TestUdfResultCheckListOfPersonStructWithBrokenIndexToDict) { FullValidateValueFunc fullValidateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder* valueBuilder, const TType* type) { Y_UNUSED(type); auto indexDict = valueBuilder->ToIndexDict(value); static_assert(RAW_BROKEN_INDEX_LIST_TO_DICT == 1, "a list is too small"); ValidateDictOfPersonStructFunc(indexDict, RAW_BROKEN_INDEX_LIST_TO_DICT - 1, RAW_BROKEN_INDEX_LIST_TO_DICT); /// verify lookup fail on broken index UNIT_ASSERT_EXCEPTION(ValidateDictOfPersonStructFunc(indexDict, RAW_BROKEN_INDEX_LIST_TO_DICT, RAW_BROKEN_INDEX_LIST_TO_DICT), TUdfValidateException); ValidateDictOfPersonStructFunc(indexDict, RAW_BROKEN_INDEX_LIST_TO_DICT + 1, RAW_BROKEN_INDEX_LIST_TO_DICT); }; ProcessSimpleUdfFunc("UtUDF.ListOfPersonStructWithBrokenIndexToDict", {}, {}, fullValidateFunc); } Y_UNIT_TEST(TestUdfResultCheckDictOfPerson) { ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder*) { auto dictIter = value.GetDictIterator(); NUdf::TUnboxedValue key, payload; for (ui32 index = 0; dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), MAKE_DICT_DIGIT2PERSON()[index].first); auto person = payload; auto firstName = person.GetElement(NUdf::PersonStruct::MetaIndexes[0]); UNIT_ASSERT_VALUES_EQUAL(TString(firstName.AsStringRef()), DICT_DIGIT2PERSON_BROKEN_CONTENT_BY_INDEX[index]->FirstName); auto lastName = person.GetElement(NUdf::PersonStruct::MetaIndexes[1]); UNIT_ASSERT_VALUES_EQUAL(TString(lastName.AsStringRef()), DICT_DIGIT2PERSON_BROKEN_CONTENT_BY_INDEX[index]->LastName); UNIT_ASSERT_VALUES_EQUAL(person.GetElement(NUdf::PersonStruct::MetaIndexes[2]).Get(), DICT_DIGIT2PERSON_BROKEN_CONTENT_BY_INDEX[index]->Age); } }; ProcessSimpleUdfFunc("UtUDF.DictOfPerson", {}, validateFunc); } Y_UNIT_TEST(TestUdfResultCheckDictOfPersonBroken) { ValidateValueFunc validateFunc = [](const NUdf::TUnboxedValuePod& value, const NUdf::IValueBuilder*) { auto dictIter = value.GetDictIterator(); NUdf::TUnboxedValue key, payload; for (ui32 index = 0; index < DICT_DIGIT2PERSON_BROKEN_PERSON_INDEX && dictIter.NextPair(key, payload); ++index) { UNIT_ASSERT_VALUES_EQUAL(key.Get(), MAKE_DICT_DIGIT2PERSON_BROKEN()[index].first); auto person = payload; auto firstName = person.GetElement(NUdf::PersonStruct::MetaIndexes[0]); UNIT_ASSERT_VALUES_EQUAL(TString(firstName.AsStringRef()), DICT_DIGIT2PERSON_BROKEN_CONTENT_BY_INDEX[index]->FirstName); auto lastName = person.GetElement(NUdf::PersonStruct::MetaIndexes[1]); UNIT_ASSERT_VALUES_EQUAL(TString(lastName.AsStringRef()), DICT_DIGIT2PERSON_BROKEN_CONTENT_BY_INDEX[index]->LastName); UNIT_ASSERT_VALUES_EQUAL(person.GetElement(NUdf::PersonStruct::MetaIndexes[2]).Get(), DICT_DIGIT2PERSON_BROKEN_CONTENT_BY_INDEX[index]->Age); } UNIT_ASSERT_EXCEPTION(dictIter.NextPair(key, payload), TUdfValidateException); }; ProcessSimpleUdfFunc("UtUDF.DictOfPersonBroken", {}, validateFunc); } } }