#pragma once #ifndef OPERATION_INL_H_ #error "Direct inclusion of this file is not allowed, use operation.h" #include "operation.h" #endif #undef OPERATION_INL_H_ #include "errors.h" #include #include #include #include #include #include #include namespace NYT { namespace NDetail { //////////////////////////////////////////////////////////////////////////////// template void Assign(TVector& array, size_t idx, const T& value) { array.resize(std::max(array.size(), idx + 1)); array[idx] = value; } //////////////////////////////////////////////////////////////////////////////// template TStructuredRowStreamDescription GetStructuredRowStreamDescription() { if constexpr (std::is_same_v) { return TTNodeStructuredRowStream{}; } else if constexpr (std::is_same_v) { return TTYaMRRowStructuredRowStream{}; } else if constexpr (std::is_same_v<::google::protobuf::Message, TRow>) { return TProtobufStructuredRowStream{nullptr}; } else if constexpr (TIsBaseOf<::google::protobuf::Message, TRow>::Value) { return TProtobufStructuredRowStream{TRow::descriptor()}; } else if constexpr (TIsProtoOneOf::value) { return TProtobufStructuredRowStream{nullptr}; } else { static_assert(TDependentFalse, "Unknown row type"); } } //////////////////////////////////////////////////////////////////////////////// } // namespace NDetail //////////////////////////////////////////////////////////////////////////////// template TStructuredTablePath Structured(TRichYPath richYPath) { return TStructuredTablePath(std::move(richYPath), StructuredTableDescription()); } template TTableStructure StructuredTableDescription() { if constexpr (std::is_same_v) { return TUnspecifiedTableStructure{}; } else if constexpr (std::is_same_v) { return TUnspecifiedTableStructure{}; } else if constexpr (std::is_base_of_v<::google::protobuf::Message, TRow>) { if constexpr (std::is_same_v<::google::protobuf::Message, TRow>) { static_assert(TDependentFalse, "Cannot use ::google::protobuf::Message as table descriptor"); } else { return TProtobufTableStructure{TRow::descriptor()}; } } else { static_assert(TDependentFalse, "Unknown row type"); } } //////////////////////////////////////////////////////////////////////////////// template TDerived& TRawOperationIoTableSpec::AddInput(const TRichYPath& path) { Inputs_.push_back(path); return static_cast(*this); } template TDerived& TRawOperationIoTableSpec::SetInput(size_t tableIndex, const TRichYPath& path) { NDetail::Assign(Inputs_, tableIndex, path); } template TDerived& TRawOperationIoTableSpec::AddOutput(const TRichYPath& path) { Outputs_.push_back(path); return static_cast(*this); } template TDerived& TRawOperationIoTableSpec::SetOutput(size_t tableIndex, const TRichYPath& path) { NDetail::Assign(Outputs_, tableIndex, path); } template const TVector& TRawOperationIoTableSpec::GetInputs() const { return Inputs_; } template const TVector& TRawOperationIoTableSpec::GetOutputs() const { return Outputs_; } //////////////////////////////////////////////////////////////////////////////// template TDerived& TRawMapReduceOperationIoSpec::AddMapOutput(const TRichYPath& path) { MapOutputs_.push_back(path); return static_cast(*this); } template TDerived& TRawMapReduceOperationIoSpec::SetMapOutput(size_t tableIndex, const TRichYPath& path) { NDetail::Assign(MapOutputs_, tableIndex, path); } template const TVector& TRawMapReduceOperationIoSpec::GetMapOutputs() const { return MapOutputs_; } //////////////////////////////////////////////////////////////////////////////// ::TIntrusivePtr CreateJobNodeReader(TRawTableReaderPtr rawTableReader); ::TIntrusivePtr CreateJobYaMRReader(TRawTableReaderPtr rawTableReader); ::TIntrusivePtr CreateJobProtoReader(TRawTableReaderPtr rawTableReader); ::TIntrusivePtr CreateJobNodeWriter(THolder rawTableWriter); ::TIntrusivePtr CreateJobYaMRWriter(THolder rawTableWriter); ::TIntrusivePtr CreateJobProtoWriter(THolder rawTableWriter); //////////////////////////////////////////////////////////////////////////////// template inline ::TIntrusivePtr::IReaderImpl> CreateJobReaderImpl(TRawTableReaderPtr rawTableReader); template <> inline ::TIntrusivePtr CreateJobReaderImpl(TRawTableReaderPtr rawTableReader) { return CreateJobNodeReader(rawTableReader); } template <> inline ::TIntrusivePtr CreateJobReaderImpl(TRawTableReaderPtr rawTableReader) { return CreateJobYaMRReader(rawTableReader); } template <> inline ::TIntrusivePtr CreateJobReaderImpl(TRawTableReaderPtr rawTableReader) { return CreateJobProtoReader(rawTableReader); } template inline ::TIntrusivePtr::IReaderImpl> CreateJobReaderImpl(TRawTableReaderPtr rawTableReader) { if constexpr (TIsBaseOf::Value || NDetail::TIsProtoOneOf::value) { return CreateJobProtoReader(rawTableReader); } else { static_assert(TDependentFalse, "Unknown row type"); } } template inline TTableReaderPtr CreateJobReader(TRawTableReaderPtr rawTableReader) { return new TTableReader(CreateJobReaderImpl(rawTableReader)); } //////////////////////////////////////////////////////////////////////////////// template TTableWriterPtr CreateJobWriter(THolder rawJobWriter); template <> inline TTableWriterPtr CreateJobWriter(THolder rawJobWriter) { return new TTableWriter(CreateJobNodeWriter(std::move(rawJobWriter))); } template <> inline TTableWriterPtr CreateJobWriter(THolder rawJobWriter) { return new TTableWriter(CreateJobYaMRWriter(std::move(rawJobWriter))); } template <> inline TTableWriterPtr CreateJobWriter(THolder rawJobWriter) { return new TTableWriter(CreateJobProtoWriter(std::move(rawJobWriter))); } template struct TProtoWriterCreator; template struct TProtoWriterCreator::Value>> { static TTableWriterPtr Create(::TIntrusivePtr writer) { return new TTableWriter(writer); } }; template inline TTableWriterPtr CreateJobWriter(THolder rawJobWriter) { if constexpr (TIsBaseOf::Value) { return TProtoWriterCreator::Create(CreateJobProtoWriter(std::move(rawJobWriter))); } else { static_assert(TDependentFalse, "Unknown row type"); } } //////////////////////////////////////////////////////////////////////////////// template void TOperationInputSpecBase::AddInput(const TRichYPath& path) { Inputs_.push_back(path); StructuredInputs_.emplace_back(Structured(path)); } template void TOperationInputSpecBase::SetInput(size_t tableIndex, const TRichYPath& path) { NDetail::Assign(Inputs_, tableIndex, path); NDetail::Assign(StructuredInputs_, tableIndex, Structured(path)); } template void TOperationOutputSpecBase::AddOutput(const TRichYPath& path) { Outputs_.push_back(path); StructuredOutputs_.emplace_back(Structured(path)); } template void TOperationOutputSpecBase::SetOutput(size_t tableIndex, const TRichYPath& path) { NDetail::Assign(Outputs_, tableIndex, path); NDetail::Assign(StructuredOutputs_, tableIndex, Structured(path)); } template template TDerived& TOperationIOSpec::AddInput(const TRichYPath& path) { static_assert(!std::is_same::value, "input type can't be Message, it can only be its strict subtype (see st.yandex-team.ru/YT-7609)"); TOperationInputSpecBase::AddInput(path); return *static_cast(this); } template template TDerived& TOperationIOSpec::SetInput(size_t tableIndex, const TRichYPath& path) { static_assert(!std::is_same::value, "input type can't be Message, it can only be its strict subtype (see st.yandex-team.ru/YT-7609)"); TOperationInputSpecBase::SetInput(tableIndex, path); return *static_cast(this); } template template TDerived& TOperationIOSpec::AddOutput(const TRichYPath& path) { static_assert(!std::is_same::value, "output type can't be Message, it can only be its strict subtype (see st.yandex-team.ru/YT-7609)"); TOperationOutputSpecBase::AddOutput(path); return *static_cast(this); } template template TDerived& TOperationIOSpec::SetOutput(size_t tableIndex, const TRichYPath& path) { static_assert(!std::is_same::value, "output type can't be Message, it can only be its strict subtype (see st.yandex-team.ru/YT-7609)"); TOperationOutputSpecBase::SetOutput(tableIndex, path); return *static_cast(this); } template TDerived& TOperationIOSpec::AddStructuredInput(TStructuredTablePath path) { TOperationInputSpecBase::AddStructuredInput(std::move(path)); return *static_cast(this); } template TDerived& TOperationIOSpec::AddStructuredOutput(TStructuredTablePath path) { TOperationOutputSpecBase::AddStructuredOutput(std::move(path)); return *static_cast(this); } //////////////////////////////////////////////////////////////////////////////// template TVanillaTask& TVanillaTask::AddOutput(const TRichYPath& path) { static_assert(!std::is_same::value, "output type can't be Message, it can only be its strict subtype (see st.yandex-team.ru/YT-7609)"); TOperationOutputSpecBase::AddOutput(path); return *this; } template TVanillaTask& TVanillaTask::SetOutput(size_t tableIndex, const TRichYPath& path) { static_assert(!std::is_same::value, "output type can't be Message, it can only be its strict subtype (see st.yandex-team.ru/YT-7609)"); TOperationOutputSpecBase::SetOutput(tableIndex, path); return *this; } //////////////////////////////////////////////////////////////////////////////// namespace NDetail { void ResetUseClientProtobuf(const char* methodName); } // namespace NDetail template TDerived& TOperationIOSpec::AddProtobufInput_VerySlow_Deprecated(const TRichYPath& path) { NDetail::ResetUseClientProtobuf("AddProtobufInput_VerySlow_Deprecated"); Inputs_.push_back(path); StructuredInputs_.emplace_back(TStructuredTablePath(path, TProtobufTableStructure{nullptr})); return *static_cast(this); } template TDerived& TOperationIOSpec::AddProtobufOutput_VerySlow_Deprecated(const TRichYPath& path) { NDetail::ResetUseClientProtobuf("AddProtobufOutput_VerySlow_Deprecated"); Outputs_.push_back(path); StructuredOutputs_.emplace_back(TStructuredTablePath(path, TProtobufTableStructure{nullptr})); return *static_cast(this); } //////////////////////////////////////////////////////////////////////////////// template TJobOperationPreparer::TInputGroup& TJobOperationPreparer::TInputGroup::Description() { for (auto i : Indices_) { Preparer_.InputDescription(i); } return *this; } template TJobOperationPreparer::TOutputGroup& TJobOperationPreparer::TOutputGroup::Description(bool inferSchema) { for (auto i : Indices_) { Preparer_.OutputDescription(i, inferSchema); } return *this; } //////////////////////////////////////////////////////////////////////////////// template TJobOperationPreparer::TInputGroup TJobOperationPreparer::BeginInputGroup(const TCont& indices) { for (auto i : indices) { ValidateInputTableIndex(i, TStringBuf("BeginInputGroup()")); } return TInputGroup(*this, TVector(std::begin(indices), std::end(indices))); } template TJobOperationPreparer::TOutputGroup TJobOperationPreparer::BeginOutputGroup(const TCont& indices) { for (auto i : indices) { ValidateOutputTableIndex(i, TStringBuf("BeginOutputGroup()")); } return TOutputGroup(*this, indices); } template TJobOperationPreparer& TJobOperationPreparer::InputDescription(int tableIndex) { ValidateMissingInputDescription(tableIndex); InputTableDescriptions_[tableIndex] = StructuredTableDescription(); return *this; } template TJobOperationPreparer& TJobOperationPreparer::OutputDescription(int tableIndex, bool inferSchema) { ValidateMissingOutputDescription(tableIndex); OutputTableDescriptions_[tableIndex] = StructuredTableDescription(); if (inferSchema && !OutputSchemas_[tableIndex]) { OutputSchemas_[tableIndex] = CreateTableSchema(); } return *this; } //////////////////////////////////////////////////////////////////////////////// template template TDerived& TIntermediateTablesHintSpec::HintMapOutput() { IntermediateMapOutputDescription_ = StructuredTableDescription(); return *static_cast(this); } template template TDerived& TIntermediateTablesHintSpec::AddMapOutput(const TRichYPath& path) { MapOutputs_.push_back(path); StructuredMapOutputs_.emplace_back(Structured(path)); return *static_cast(this); } template template TDerived& TIntermediateTablesHintSpec::HintReduceCombinerInput() { IntermediateReduceCombinerInputDescription_ = StructuredTableDescription(); return *static_cast(this); } template template TDerived& TIntermediateTablesHintSpec::HintReduceCombinerOutput() { IntermediateReduceCombinerOutputDescription_ = StructuredTableDescription(); return *static_cast(this); } template template TDerived& TIntermediateTablesHintSpec::HintReduceInput() { IntermediateReducerInputDescription_ = StructuredTableDescription(); return *static_cast(this); } template const TVector& TIntermediateTablesHintSpec::GetStructuredMapOutputs() const { return StructuredMapOutputs_; } template const TMaybe& TIntermediateTablesHintSpec::GetIntermediateMapOutputDescription() const { return IntermediateMapOutputDescription_; } template const TMaybe& TIntermediateTablesHintSpec::GetIntermediateReduceCombinerInputDescription() const { return IntermediateReduceCombinerInputDescription_; } template const TMaybe& TIntermediateTablesHintSpec::GetIntermediateReduceCombinerOutputDescription() const { return IntermediateReduceCombinerOutputDescription_; } template const TMaybe& TIntermediateTablesHintSpec::GetIntermediateReducerInputDescription() const { return IntermediateReducerInputDescription_; } //////////////////////////////////////////////////////////////////////////////// struct TReducerContext { bool Break = false; static TReducerContext* Get() { return Singleton(); } }; template inline void IReducer::Break() { TReducerContext::Get()->Break = true; } template void FeedJobInput( IMapper* mapper, typename TRowTraits::IReaderImpl* readerImpl, TWriter* writer) { using TInputRow = typename TReader::TRowType; auto reader = MakeIntrusive>(readerImpl); mapper->Do(reader.Get(), writer); } template void FeedJobInput( IReducer* reducer, typename TRowTraits::IReaderImpl* readerImpl, TWriter* writer) { using TInputRow = typename TReader::TRowType; auto rangesReader = MakeIntrusive>(readerImpl); for (; rangesReader->IsValid(); rangesReader->Next()) { reducer->Do(&rangesReader->GetRange(), writer); if (TReducerContext::Get()->Break) { break; } } } template void FeedJobInput( IAggregatorReducer* reducer, typename TRowTraits::IReaderImpl* readerImpl, TWriter* writer) { using TInputRow = typename TReader::TRowType; auto rangesReader = MakeIntrusive>(readerImpl); reducer->Do(rangesReader.Get(), writer); } template int RunRawJob(size_t outputTableCount, IInputStream& jobStateStream) { TRawJobContext context(outputTableCount); TRawJob job; job.Load(jobStateStream); job.Do(context); return 0; } template <> inline int RunRawJob(size_t /* outputTableCount */, IInputStream& /* jobStateStream */) { Y_ABORT(); } template int RunVanillaJob(size_t outputTableCount, IInputStream& jobStateStream) { TVanillaJob job; job.Load(jobStateStream); if constexpr (std::is_base_of, TVanillaJob>::value) { Y_ABORT_UNLESS(outputTableCount == 0, "Void vanilla job expects zero 'outputTableCount'"); job.Do(); } else { Y_ABORT_UNLESS(outputTableCount, "Vanilla job with table writer expects nonzero 'outputTableCount'"); using TOutputRow = typename TVanillaJob::TWriter::TRowType; THolder rawJobWriter; if (auto customWriter = job.CreateCustomRawJobWriter(outputTableCount)) { rawJobWriter = std::move(customWriter); } else { rawJobWriter = CreateRawJobWriter(outputTableCount); } auto writer = CreateJobWriter(std::move(rawJobWriter)); job.Start(writer.Get()); job.Do(writer.Get()); job.Finish(writer.Get()); writer->Finish(); } return 0; } template <> inline int RunVanillaJob(size_t /* outputTableCount */, IInputStream& /* jobStateStream */) { Y_ABORT(); } template requires TIsBaseOf::Value int RunJob(size_t outputTableCount, IInputStream& jobStateStream) { using TInputRow = typename TJob::TReader::TRowType; using TOutputRow = typename TJob::TWriter::TRowType; auto job = MakeIntrusive(); job->Load(jobStateStream); TRawTableReaderPtr rawJobReader; if (auto customReader = job->CreateCustomRawJobReader(/*fd*/ 0)) { rawJobReader = customReader; } else { rawJobReader = CreateRawJobReader(/*fd*/ 0); } auto readerImpl = CreateJobReaderImpl(rawJobReader); // Many users don't expect to have jobs with empty input so we skip such jobs. if (!readerImpl->IsValid()) { return 0; } THolder rawJobWriter; if (auto customWriter = job->CreateCustomRawJobWriter(outputTableCount)) { rawJobWriter = std::move(customWriter); } else { rawJobWriter = CreateRawJobWriter(outputTableCount); } auto writer = CreateJobWriter(std::move(rawJobWriter)); job->Start(writer.Get()); FeedJobInput(job.Get(), readerImpl.Get(), writer.Get()); job->Finish(writer.Get()); writer->Finish(); return 0; } // // We leave RunMapJob/RunReduceJob/RunAggregatorReducer for backward compatibility, // some user use them already. :( template int RunMapJob(size_t outputTableCount, IInputStream& jobStateStream) { return RunJob(outputTableCount, jobStateStream); } template int RunReduceJob(size_t outputTableCount, IInputStream& jobStateStream) { return RunJob(outputTableCount, jobStateStream); } template int RunAggregatorReducer(size_t outputTableCount, IInputStream& jobStateStream) { return RunJob(outputTableCount, jobStateStream); } //////////////////////////////////////////////////////////////////////////////// template struct TIsConstructibleFromNode : std::false_type { }; template struct TIsConstructibleFromNode()))>> : std::true_type { }; template ::TIntrusivePtr ConstructJobFromNode(const TNode& node) { if constexpr (TIsConstructibleFromNode::value) { Y_ENSURE(node.GetType() != TNode::Undefined, "job has FromNode method but constructor arguments were not provided"); return TJob::FromNode(node); } else { Y_ENSURE(node.GetType() == TNode::Undefined, "constructor arguments provided but job does not contain FromNode method"); return MakeIntrusive(); } } //////////////////////////////////////////////////////////////////////////////// using TJobFunction = int (*)(size_t, IInputStream&); using TConstructJobFunction = ::TIntrusivePtr (*)(const TNode&); class TJobFactory { public: static TJobFactory* Get() { return Singleton(); } template void RegisterJob(const char* name) { RegisterJobImpl(name, RunJob); JobConstructors[name] = ConstructJobFromNode; } template void RegisterRawJob(const char* name) { RegisterJobImpl(name, RunRawJob); } template void RegisterVanillaJob(const char* name) { RegisterJobImpl(name, RunVanillaJob); } TString GetJobName(const IJob* job) { const auto typeIndex = std::type_index(typeid(*job)); CheckJobRegistered(typeIndex); return JobNames[typeIndex]; } TJobFunction GetJobFunction(const char* name) { CheckNameRegistered(name); return JobFunctions[name]; } TConstructJobFunction GetConstructingFunction(const char* name) { CheckNameRegistered(name); return JobConstructors[name]; } private: TMap JobNames; THashMap JobFunctions; THashMap JobConstructors; template void RegisterJobImpl(const char* name, TRunner runner) { const auto typeIndex = std::type_index(typeid(TJob)); CheckNotRegistered(typeIndex, name); JobNames[typeIndex] = name; JobFunctions[name] = runner; } void CheckNotRegistered(const std::type_index& typeIndex, const char* name) { Y_ENSURE(!JobNames.contains(typeIndex), "type_info '" << typeIndex.name() << "'" "is already registered under name '" << JobNames[typeIndex] << "'"); Y_ENSURE(!JobFunctions.contains(name), "job with name '" << name << "' is already registered"); } void CheckJobRegistered(const std::type_index& typeIndex) { Y_ENSURE(JobNames.contains(typeIndex), "type_info '" << typeIndex.name() << "' is not registered, use REGISTER_* macros"); } void CheckNameRegistered(const char* name) { Y_ENSURE(JobFunctions.contains(name), "job with name '" << name << "' is not registered, use REGISTER_* macros"); } }; //////////////////////////////////////////////////////////////////////////////// template struct TMapperRegistrator { TMapperRegistrator(const char* name) { static_assert(TMapper::JobType == IJob::EType::Mapper, "REGISTER_MAPPER is not compatible with this job class"); NYT::TJobFactory::Get()->RegisterJob(name); } }; template struct TReducerRegistrator { TReducerRegistrator(const char* name) { static_assert(TReducer::JobType == IJob::EType::Reducer || TReducer::JobType == IJob::EType::ReducerAggregator, "REGISTER_REDUCER is not compatible with this job class"); NYT::TJobFactory::Get()->RegisterJob(name); } }; template struct TRawJobRegistrator { TRawJobRegistrator(const char* name) { static_assert(TRawJob::JobType == IJob::EType::RawJob, "REGISTER_RAW_JOB is not compatible with this job class"); NYT::TJobFactory::Get()->RegisterRawJob(name); } }; template struct TVanillaJobRegistrator { TVanillaJobRegistrator(const char* name) { static_assert(TVanillaJob::JobType == IJob::EType::VanillaJob, "REGISTER_VANILLA_JOB is not compatible with this job class"); NYT::TJobFactory::Get()->RegisterVanillaJob(name); } }; //////////////////////////////////////////////////////////////////////////////// inline TString YtRegistryTypeName(const TString& name) { TString res = name; #ifdef _win_ SubstGlobal(res, "class ", ""); #endif return res; } //////////////////////////////////////////////////////////////////////////////// #define REGISTER_MAPPER(...) \ static const NYT::TMapperRegistrator<__VA_ARGS__> \ Y_GENERATE_UNIQUE_ID(TJobRegistrator)(NYT::YtRegistryTypeName(TypeName<__VA_ARGS__>()).data()); #define REGISTER_NAMED_MAPPER(name, ...) \ static const NYT::TMapperRegistrator<__VA_ARGS__> \ Y_GENERATE_UNIQUE_ID(TJobRegistrator)(name); #define REGISTER_REDUCER(...) \ static const NYT::TReducerRegistrator<__VA_ARGS__> \ Y_GENERATE_UNIQUE_ID(TJobRegistrator)(NYT::YtRegistryTypeName(TypeName<__VA_ARGS__>()).data()); #define REGISTER_NAMED_REDUCER(name, ...) \ static const NYT::TReducerRegistrator<__VA_ARGS__> \ Y_GENERATE_UNIQUE_ID(TJobRegistrator)(name); #define REGISTER_NAMED_RAW_JOB(name, ...) \ static const NYT::TRawJobRegistrator<__VA_ARGS__> \ Y_GENERATE_UNIQUE_ID(TJobRegistrator)(name); #define REGISTER_RAW_JOB(...) \ REGISTER_NAMED_RAW_JOB((NYT::YtRegistryTypeName(TypeName<__VA_ARGS__>()).data()), __VA_ARGS__) #define REGISTER_NAMED_VANILLA_JOB(name, ...) \ static NYT::TVanillaJobRegistrator<__VA_ARGS__> \ Y_GENERATE_UNIQUE_ID(TJobRegistrator)(name); #define REGISTER_VANILLA_JOB(...) \ REGISTER_NAMED_VANILLA_JOB((NYT::YtRegistryTypeName(TypeName<__VA_ARGS__>()).data()), __VA_ARGS__) //////////////////////////////////////////////////////////////////////////////// template TStructuredRowStreamDescription IMapper::GetInputRowStreamDescription() const { return NYT::NDetail::GetStructuredRowStreamDescription(); } template TStructuredRowStreamDescription IMapper::GetOutputRowStreamDescription() const { return NYT::NDetail::GetStructuredRowStreamDescription(); } //////////////////////////////////////////////////////////////////////////////// template TStructuredRowStreamDescription IReducer::GetInputRowStreamDescription() const { return NYT::NDetail::GetStructuredRowStreamDescription(); } template TStructuredRowStreamDescription IReducer::GetOutputRowStreamDescription() const { return NYT::NDetail::GetStructuredRowStreamDescription(); } //////////////////////////////////////////////////////////////////////////////// template TStructuredRowStreamDescription IAggregatorReducer::GetInputRowStreamDescription() const { return NYT::NDetail::GetStructuredRowStreamDescription(); } template TStructuredRowStreamDescription IAggregatorReducer::GetOutputRowStreamDescription() const { return NYT::NDetail::GetStructuredRowStreamDescription(); } //////////////////////////////////////////////////////////////////////////////// template TStructuredRowStreamDescription IVanillaJob::GetInputRowStreamDescription() const { return TVoidStructuredRowStream(); } template TStructuredRowStreamDescription IVanillaJob::GetOutputRowStreamDescription() const { return NYT::NDetail::GetStructuredRowStreamDescription(); } //////////////////////////////////////////////////////////////////////////////// } // namespace NYT