diff options
author | imunkin <[email protected]> | 2025-06-18 11:27:02 +0300 |
---|---|---|
committer | imunkin <[email protected]> | 2025-06-18 13:27:13 +0300 |
commit | 06674e69d9005bafa2ac27df970398fd1c389fdf (patch) | |
tree | af1d06bf39a7988e3439580940b9c540c3e40dc4 | |
parent | f2030b6e6ffca86485107e3c699f01a555bcf45b (diff) |
YQL-20039: Make Datetime::Format work with basic resources for old MKQL versions
commit_hash:cc278d11897e8f36249bd3b285613b61c853d034
-rw-r--r-- | yql/essentials/minikql/comp_nodes/mkql_udf.cpp | 183 | ||||
-rw-r--r-- | yql/essentials/minikql/comp_nodes/ut/mkql_udf_ut.cpp | 371 |
2 files changed, 530 insertions, 24 deletions
diff --git a/yql/essentials/minikql/comp_nodes/mkql_udf.cpp b/yql/essentials/minikql/comp_nodes/mkql_udf.cpp index 44c45888c0a..f81d4f2bc49 100644 --- a/yql/essentials/minikql/comp_nodes/mkql_udf.cpp +++ b/yql/essentials/minikql/comp_nodes/mkql_udf.cpp @@ -8,6 +8,7 @@ #include <yql/essentials/minikql/mkql_node_printer.h> #include <yql/essentials/minikql/mkql_type_builder.h> #include <yql/essentials/minikql/mkql_utils.h> +#include <yql/essentials/minikql/datetime/datetime64.h> #include <yql/essentials/utils/yql_panic.h> #include <library/cpp/containers/stack_array/stack_array.h> @@ -27,6 +28,46 @@ TString TruncateTypeDiff(const TString& s) { return s.substr(0,TypeDiffLimit) + "..."; } +static const char TMResourceName[] = "DateTime2.TM"; +static const char TM64ResourceName[] = "DateTime2.TM64"; +// XXX: This class implements the wrapper to properly handle the +// case when the signature of the emitted callable (i.e. callable +// type) requires the extended datetime resource as an argument, +// but the basic one is given. It wraps the unboxed value with +// the closure to add the bridge with the implicit datetime +// resource conversion. +class TDateTimeConvertWrapper: public NUdf::TBoxedValue { +public: + TDateTimeConvertWrapper(NUdf::TUnboxedValue&& callable) + : Callable_(callable) + {}; + +private: + NUdf::TUnboxedValue Run(const NUdf::IValueBuilder* valueBuilder, const NUdf::TUnboxedValuePod* args) const final { + return NUdf::TUnboxedValuePod(new TDateTimeConverter(Callable_.Run(valueBuilder, args))); + } + + class TDateTimeConverter: public NUdf::TBoxedValue { + public: + TDateTimeConverter(NUdf::TUnboxedValue&& closure) + : Closure_(closure) + {} + private: + NUdf::TUnboxedValue Run(const NUdf::IValueBuilder* valueBuilder, const NUdf::TUnboxedValuePod* args) const final { + NUdf::TUnboxedValuePod newArg; + const auto arg = args[0]; + const auto& narrow = *reinterpret_cast<const NYql::DateTime::TTMStorage*>(arg.GetRawPtr()); + auto& extended = *reinterpret_cast<NYql::DateTime::TTM64Storage*>(newArg.GetRawPtr()); + extended.From(narrow); + return Closure_.Run(valueBuilder, &newArg); + } + + const NUdf::TUnboxedValue Closure_; + }; + + const NUdf::TUnboxedValue Callable_; +}; + template<class TValidatePolicy, class TValidateMode> class TSimpleUdfWrapper: public TMutableComputationNode<TSimpleUdfWrapper<TValidatePolicy,TValidateMode>> { using TBaseComputation = TMutableComputationNode<TSimpleUdfWrapper<TValidatePolicy,TValidateMode>>; @@ -38,7 +79,8 @@ public: NUdf::TSourcePosition pos, const TCallableType* callableType, const TCallableType* functionType, - TType* userType) + TType* userType, + bool wrapDateTimeConvert) : TBaseComputation(mutables, EValueRepresentation::Boxed) , FunctionName(std::move(functionName)) , TypeConfig(std::move(typeConfig)) @@ -46,6 +88,7 @@ public: , CallableType(callableType) , FunctionType(functionType) , UserType(userType) + , WrapDateTimeConvert(wrapDateTimeConvert) { this->Stateless = false; } @@ -69,6 +112,7 @@ public: NUdf::TUnboxedValue udf(NUdf::TUnboxedValuePod(funcInfo.Implementation.Release())); TValidate<TValidatePolicy,TValidateMode>::WrapCallable(FunctionType, udf, TStringBuilder() << "FunctionWithConfig<" << FunctionName << ">"); ExtendArgs(udf, CallableType, funcInfo.FunctionType); + ConvertDateTimeArg(udf); return udf.Release(); } private: @@ -109,6 +153,12 @@ private: } } + void ConvertDateTimeArg(NUdf::TUnboxedValue& callable) const { + if (WrapDateTimeConvert) { + callable = NUdf::TUnboxedValuePod(new TDateTimeConvertWrapper(std::move(callable))); + } + } + void RegisterDependencies() const final {} const TString FunctionName; @@ -117,6 +167,7 @@ private: const TCallableType *const CallableType; const TCallableType *const FunctionType; TType *const UserType; + bool WrapDateTimeConvert; }; class TUdfRunCodegeneratorNode: public TSimpleUdfWrapper<TValidateErrorPolicyNone, TValidateModeLazy<TValidateErrorPolicyNone>> @@ -133,11 +184,12 @@ public: const TCallableType* callableType, const TCallableType* functionType, TType* userType, + bool wrapDateTimeConvert, TString&& moduleIRUniqID, TString&& moduleIR, TString&& fuctioNameIR, NUdf::TUniquePtr<NUdf::IBoxedValue>&& impl) - : TSimpleUdfWrapper(mutables, std::move(functionName), std::move(typeConfig), pos, callableType, functionType, userType) + : TSimpleUdfWrapper(mutables, std::move(functionName), std::move(typeConfig), pos, callableType, functionType, userType, wrapDateTimeConvert) , ModuleIRUniqID(std::move(moduleIRUniqID)) , ModuleIR(std::move(moduleIR)) , IRFunctionName(std::move(fuctioNameIR)) @@ -181,7 +233,8 @@ public: IComputationNode* runConfigNode, ui32 runConfigArgs, const TCallableType* callableType, - TType* userType) + TType* userType, + bool wrapDateTimeConvert) : TBaseComputation(mutables, EValueRepresentation::Boxed) , FunctionName(std::move(functionName)) , TypeConfig(std::move(typeConfig)) @@ -190,6 +243,7 @@ public: , RunConfigArgs(runConfigArgs) , CallableType(callableType) , UserType(userType) + , WrapDateTimeConvert(wrapDateTimeConvert) , UdfIndex(mutables.CurValueIndex++) { this->Stateless = false; @@ -200,6 +254,7 @@ public: if (!udf.HasValue()) { MakeUdf(ctx, udf); } + ConvertDateTimeArg(udf); NStackArray::TStackArray<NUdf::TUnboxedValue> args(ALLOC_ON_STACK(NUdf::TUnboxedValue, RunConfigArgs)); args[0] = RunConfigNode->GetValue(ctx); auto callable = udf.Run(ctx.Builder, args.data()); @@ -233,6 +288,11 @@ public: block = main; + const auto convertFunc = ConstantInt::get(Type::getInt64Ty(context), GetMethodPtr<&TUdfWrapper::ConvertDateTimeArg>()); + const auto convertType = FunctionType::get(Type::getVoidTy(context), {self->getType(), udfPtr->getType()}, false); + const auto convertFuncPtr = CastInst::Create(Instruction::IntToPtr, convertFunc, PointerType::getUnqual(convertType), "convert", block); + CallInst::Create(convertType, convertFuncPtr, {self, udfPtr}, "", block); + const auto argsType = ArrayType::get(valueType, RunConfigArgs); const auto args = new AllocaInst(argsType, 0U, "args", block); const auto zero = ConstantInt::get(indexType, 0); @@ -283,6 +343,12 @@ private: TValidate<TValidatePolicy,TValidateMode>::WrapCallable(CallableType, callable, TStringBuilder() << "FunctionWithConfig<" << FunctionName << ">"); } + void ConvertDateTimeArg(NUdf::TUnboxedValue& callable) const { + if (WrapDateTimeConvert) { + callable = NUdf::TUnboxedValuePod(new TDateTimeConvertWrapper(std::move(callable))); + } + } + void RegisterDependencies() const final { this->DependsOn(RunConfigNode); } @@ -294,6 +360,7 @@ private: const ui32 RunConfigArgs; const TCallableType* CallableType; TType* const UserType; + bool WrapDateTimeConvert; const ui32 UdfIndex; }; @@ -323,6 +390,63 @@ inline IComputationNode* CreateUdfWrapper(const TComputationNodeFactoryContext& }; } +// XXX: The helper below allows to make a stitchless upgrade +// of MKQL runtime, regarding the incompatible changes made for +// DateTime::Format UDF. +template<bool Extended> +static bool IsDateTimeResource(const TType* type) { + if (!type->IsResource()) { + return false; + } + const auto resourceName = AS_TYPE(TResourceType, type)->GetTag(); + + if constexpr (Extended) { + return resourceName == NUdf::TStringRef::Of(TM64ResourceName); + } else { + return resourceName == NUdf::TStringRef::Of(TMResourceName); + } +} + +static bool IsDateTimeConvertible(const NUdf::TStringRef& funcName, + const TCallableType* nodeType, + const TCallableType* funcType, + bool& needConvert) +{ + Y_DEBUG_ABORT_UNLESS(!needConvert); + if (funcName == NUdf::TStringRef::Of("DateTime2.Format")) { + Y_DEBUG_ABORT_UNLESS(nodeType->GetArgumentsCount()); + Y_DEBUG_ABORT_UNLESS(funcType->GetArgumentsCount()); + // XXX: In general, DateTime resources are not convertible + // in runtime, but for the stitchless upgrade of MKQL + // runtime, we consider the basic resource, being + // convertible to the extended one for DateTime2.Format... + if (IsDateTimeResource<false>(nodeType->GetArgumentType(0)) && + IsDateTimeResource<true>(funcType->GetArgumentType(0))) + { + // XXX: ... and to implicitly convert the basic resource + // to the extended one, the closure has to be wrapped by + // DateTimeConvertWrapper. + needConvert = true; + return true; + } + } + if (funcName == NUdf::TStringRef::Of("DateTime2.Convert")) { + // XXX: Vice versa convertion is forbidden as well, but + // for the stitchless upgrade of MKQL runtime, we consider + // the extended resource, being compatible with the basic + // one for the return type of DateTime2.Convert. + if (IsDateTimeResource<true>(nodeType->GetReturnType()) && + IsDateTimeResource<false>(funcType->GetReturnType())) + { + // XXX: However, DateTime2.Convert has to convert the + // basic resource to the extended one. + needConvert = false; + return true; + } + } + return false; +} + } IComputationNode* WrapUdf(TCallable& callable, const TComputationNodeFactoryContext& ctx) { @@ -359,6 +483,7 @@ IComputationNode* WrapUdf(TCallable& callable, const TComputationNodeFactoryCont << status.GetError()).c_str()); } + bool wrapDateTimeConvert = false; const auto callableFuncType = AS_TYPE(TCallableType, funcInfo.FunctionType); const auto callableNodeType = AS_TYPE(TCallableType, callable.GetType()->GetReturnType()); const auto runConfigFuncType = funcInfo.RunConfigType; @@ -417,34 +542,44 @@ IComputationNode* WrapUdf(TCallable& callable, const TComputationNodeFactoryCont } const auto closureFuncType = runConfigNodeType->IsVoid() ? callableFuncType - : AS_TYPE(TCallableType, callableFuncType)->GetReturnType(); + : AS_TYPE(TCallableType, callableFuncType->GetReturnType()); const auto closureNodeType = runConfigNodeType->IsVoid() - ? AS_TYPE(TCallableType, callableNodeType)->GetReturnType() + ? AS_TYPE(TCallableType, callableNodeType->GetReturnType()) : callableNodeType; if (!closureNodeType->IsConvertableTo(*closureFuncType)) { - TString diff = TStringBuilder() - << "type mismatch, expected return type: " - << PrintNode(closureNodeType, true) - << ", actual: " - << PrintNode(closureFuncType, true); - UdfTerminate((TStringBuilder() << pos - << " Udf Function '" - << funcName - << "' " - << TruncateTypeDiff(diff)).c_str()); + if (!IsDateTimeConvertible(funcName, closureNodeType, closureFuncType, wrapDateTimeConvert)) { + TString diff = TStringBuilder() + << "type mismatch, expected return type: " + << PrintNode(closureNodeType, true) + << ", actual: " + << PrintNode(closureFuncType, true); + UdfTerminate((TStringBuilder() << pos + << " Udf Function '" + << funcName + << "' " + << TruncateTypeDiff(diff)).c_str()); + } + MKQL_ENSURE(funcName == NUdf::TStringRef::Of("DateTime2.Format") || + funcName == NUdf::TStringRef::Of("DateTime2.Convert"), + "Unexpected function violates the convertible invariants"); } const auto runConfigCompNode = LocateNode(ctx.NodeLocator, *runCfgNode.GetNode()); const auto runConfigArgs = funcInfo.FunctionType->GetArgumentsCount(); return runConfigNodeType->IsVoid() - ? CreateUdfWrapper<true>(ctx, std::move(funcName), std::move(typeConfig), pos, callableNodeType, callableFuncType, userType) - : CreateUdfWrapper<false>(ctx, std::move(funcName), std::move(typeConfig), pos, runConfigCompNode, runConfigArgs, callableNodeType, userType); + ? CreateUdfWrapper<true>(ctx, std::move(funcName), std::move(typeConfig), pos, callableNodeType, callableFuncType, userType, wrapDateTimeConvert) + : CreateUdfWrapper<false>(ctx, std::move(funcName), std::move(typeConfig), pos, runConfigCompNode, runConfigArgs, callableNodeType, userType, wrapDateTimeConvert); } if (!callableFuncType->IsConvertableTo(*callableNodeType, true)) { - TString diff = TStringBuilder() << "type mismatch, expected return type: " << PrintNode(callableNodeType, true) << - ", actual:" << PrintNode(callableFuncType, true); - UdfTerminate((TStringBuilder() << pos << " UDF Function '" << funcName << "' " << TruncateTypeDiff(diff)).c_str()); + if (!IsDateTimeConvertible(funcName, callableNodeType, callableFuncType, wrapDateTimeConvert)) { + TString diff = TStringBuilder() << "type mismatch, expected return type: " << PrintNode(callableNodeType, true) << + ", actual:" << PrintNode(callableFuncType, true); + UdfTerminate((TStringBuilder() << pos << " UDF Function '" << funcName << "' " << TruncateTypeDiff(diff)).c_str()); + } + MKQL_ENSURE(funcName == NUdf::TStringRef::Of("DateTime2.Format") || + funcName == NUdf::TStringRef::Of("DateTime2.Convert"), + "Unexpected function violates the convertible invariants"); } if (!funcInfo.Implementation) { @@ -454,15 +589,15 @@ IComputationNode* WrapUdf(TCallable& callable, const TComputationNodeFactoryCont if (runConfigFuncType->IsVoid()) { if (ctx.ValidateMode == NUdf::EValidateMode::None && funcInfo.ModuleIR && funcInfo.IRFunctionName) { return new TUdfRunCodegeneratorNode( - ctx.Mutables, std::move(funcName), std::move(typeConfig), pos, callableNodeType, callableFuncType, userType, + ctx.Mutables, std::move(funcName), std::move(typeConfig), pos, callableNodeType, callableFuncType, userType, wrapDateTimeConvert, std::move(funcInfo.ModuleIRUniqID), std::move(funcInfo.ModuleIR), std::move(funcInfo.IRFunctionName), std::move(funcInfo.Implementation) ); } - return CreateUdfWrapper<true>(ctx, std::move(funcName), std::move(typeConfig), pos, callableNodeType, callableFuncType, userType); + return CreateUdfWrapper<true>(ctx, std::move(funcName), std::move(typeConfig), pos, callableNodeType, callableFuncType, userType, wrapDateTimeConvert); } const auto runCfgCompNode = LocateNode(ctx.NodeLocator, *runCfgNode.GetNode()); - return CreateUdfWrapper<false>(ctx, std::move(funcName), std::move(typeConfig), pos, runCfgCompNode, 1U, callableNodeType, userType); + return CreateUdfWrapper<false>(ctx, std::move(funcName), std::move(typeConfig), pos, runCfgCompNode, 1U, callableNodeType, userType, wrapDateTimeConvert); } IComputationNode* WrapScriptUdf(TCallable& callable, const TComputationNodeFactoryContext& ctx) { @@ -511,7 +646,7 @@ IComputationNode* WrapScriptUdf(TCallable& callable, const TComputationNodeFacto const auto funcTypeInfo = static_cast<TCallableType*>(callableResultType); const auto programCompNode = LocateNode(ctx.NodeLocator, *programNode.GetNode()); - return CreateUdfWrapper<false>(ctx, std::move(funcName), std::move(typeConfig), pos, programCompNode, 1U, funcTypeInfo, userType); + return CreateUdfWrapper<false>(ctx, std::move(funcName), std::move(typeConfig), pos, programCompNode, 1U, funcTypeInfo, userType, false); } } diff --git a/yql/essentials/minikql/comp_nodes/ut/mkql_udf_ut.cpp b/yql/essentials/minikql/comp_nodes/ut/mkql_udf_ut.cpp index fe7e32e189d..590330e9f8f 100644 --- a/yql/essentials/minikql/comp_nodes/ut/mkql_udf_ut.cpp +++ b/yql/essentials/minikql/comp_nodes/ut/mkql_udf_ut.cpp @@ -2,6 +2,7 @@ #include <yql/essentials/public/udf/udf_helpers.h> #include <yql/essentials/minikql/mkql_node_serialization.h> #include <yql/essentials/minikql/mkql_node_cast.h> +#include <yql/essentials/minikql/datetime/datetime64.h> namespace NKikimr { namespace NMiniKQL { @@ -531,5 +532,375 @@ Y_UNIT_TEST_SUITE(TMiniKQLUdfTest) { } } // Y_UNIT_TEST_SUITE +// XXX: Test the hack with on-the-fly argument convertion for the +// call of the Datetime::Format UDF with the basic date resource +// parameter against the underline function, expecting the +// extended date resource as an argument. + +namespace { + +extern const char TMResourceName[] = "DateTime2.TM"; +extern const char TM64ResourceName[] = "DateTime2.TM64"; + +enum class EBuilds { + A, // DateTime::Format build with the single parameter: basic + // DateTime resource representing the timestamp argument. + B, // DateTime::Format build with two parameters: + // * the required one with the **basic** resource. + // * the optional one to tweak the Format behaviour. + C, // DateTime::Format build with two parameters: + // * the required one with the **extended** resource. + // * the optional one to tweak the Format behaviour. +}; + +template<const char* TResourceName, typename TValue, + typename TStorage = std::conditional_t<TResourceName == TMResourceName, + NYql::DateTime::TTMStorage, + NYql::DateTime::TTM64Storage>> +TStorage& Reference(TValue& value) { + return *reinterpret_cast<TStorage*>(value.GetRawPtr()); +} + +template<const char* TResourceName, typename TValue, + typename TStorage = std::conditional_t<TResourceName == TMResourceName, + NYql::DateTime::TTMStorage, + NYql::DateTime::TTM64Storage>> +const TStorage& Reference(const TValue& value) { + return *reinterpret_cast<const TStorage*>(value.GetRawPtr()); +} + +static TRuntimeNode NewDateTimeNode(const NYql::NUdf::TStringRef& dateLiteral, + const TTypeEnvironment& env) +{ + const auto dtval = ValueFromString(NYql::NUdf::EDataSlot::Datetime, dateLiteral); + return TRuntimeNode(BuildDataLiteral(dtval, NUdf::TDataType<NYql::NUdf::TDatetime>::Id, env), true); +} + +template<enum EBuilds Build, bool LoweredRuntimeVersion> +class TTestDateTime2Format : public NYql::NUdf::TBoxedValue { +static_assert(Build != EBuilds::A || LoweredRuntimeVersion, + "Build 'A' provides only the 'lowered' runtime version"); +public: + static const NYql::NUdf::TStringRef& Name() { + static auto name = NYql::NUdf::TStringRef::Of("Format"); + return name; + } + + static bool DeclareSignature(const NYql::NUdf::TStringRef& name, + NYql::NUdf::TType*, + NYql::NUdf::IFunctionTypeInfoBuilder& builder, + bool typesOnly) + { + if (Name() != name) { + return false; + } + + // FIXME: The condition below is required to untie the + // Gordian knot with the upgrade, when two MiniKQL + // runtimes with different versions are being used. + // See YQL-19967 for more info. + if (LoweredRuntimeVersion && typesOnly) { + builder.SimpleSignature<char*(NYql::NUdf::TAutoMap<NYql::NUdf::TResource<TMResourceName>>)>(); + builder.RunConfig<char*>(); + return true; + } + + switch (Build) { + case EBuilds::A: + builder.SimpleSignature<char*(NYql::NUdf::TAutoMap<NYql::NUdf::TResource<TMResourceName>>)>(); + builder.RunConfig<char*>(); + break; + case EBuilds::B: + builder.OptionalArgs(1).Args()->Add<char*>() + .Add<NYql::NUdf::TOptional<bool>>().Name("AlwaysWriteFractionalSeconds"); + builder.Returns( + builder.SimpleSignatureType<char*(NYql::NUdf::TAutoMap<NYql::NUdf::TResource<TMResourceName>>)>()); + break; + case EBuilds::C: + builder.OptionalArgs(1).Args()->Add<char*>() + .Add<NYql::NUdf::TOptional<bool>>().Name("AlwaysWriteFractionalSeconds"); + builder.Returns( + builder.SimpleSignatureType<char*(NYql::NUdf::TAutoMap<NYql::NUdf::TResource<TM64ResourceName>>)>()); + break; + } + + if (!typesOnly) { + builder.Implementation(new TTestDateTime2Format); + } + + return true; + } + +private: + NYql::NUdf::TUnboxedValue Run(const NYql::NUdf::IValueBuilder*, + const NYql::NUdf::TUnboxedValuePod* args) + const override { + bool alwaysWriteFractionalSeconds = false; + + if constexpr (Build != EBuilds::A) { + if (auto val = args[1]) { + alwaysWriteFractionalSeconds = val.Get<bool>(); + } + } + + return NYql::NUdf::TUnboxedValuePod(new TTestDateTime2Formatter(args[0], alwaysWriteFractionalSeconds)); + } + + class TTestDateTime2Formatter : public NYql::NUdf::TBoxedValue { + public: + TTestDateTime2Formatter(NYql::NUdf::TUnboxedValue format, bool alwaysWriteFractionalSeconds) + : Format_(format) + { + UNIT_ASSERT(!alwaysWriteFractionalSeconds); + } + + private: + NYql::NUdf::TUnboxedValue Run(const NYql::NUdf::IValueBuilder* valueBuilder, + const NYql::NUdf::TUnboxedValuePod* args) + const override { + TStringBuilder result; + result << Format_.AsStringRef() << ": "; + if constexpr (Build == EBuilds::C) { + const auto storage = Reference<TM64ResourceName>(args[0]); + result << storage.Day << "/" << storage.Month << "/" + << storage.Year << " " << storage.Hour << ":" + << storage.Minute << ":" << storage.Second; + } else { + const auto storage = Reference<TMResourceName>(args[0]); + result << storage.Day << "/" << storage.Month << "/" + << storage.Year << " " << storage.Hour << ":" + << storage.Minute << ":" << storage.Second; + } + result << "."; + return valueBuilder->NewString(result); + } + + const NYql::NUdf::TUnboxedValue Format_; + }; +}; + +template<bool LoweredRuntimeVersion> +class TTestDateTime2Convert : public NYql::NUdf::TBoxedValue { +public: + static const NYql::NUdf::TStringRef& Name() { + static auto name = NYql::NUdf::TStringRef::Of("Convert"); + return name; + } + + static bool DeclareSignature(const NYql::NUdf::TStringRef& name, + NYql::NUdf::TType*, + NYql::NUdf::IFunctionTypeInfoBuilder& builder, + bool typesOnly) + { + if (Name() != name) { + return false; + } + + if (LoweredRuntimeVersion && typesOnly) { + builder.SimpleSignature<NYql::NUdf::TResource<TMResourceName>(NYql::NUdf::TAutoMap<NYql::NUdf::TResource<TMResourceName>>)>() + .IsStrict(); + return true; + } + + builder.SimpleSignature<NYql::NUdf::TResource<TM64ResourceName>(NYql::NUdf::TAutoMap<NYql::NUdf::TResource<TMResourceName>>)>() + .IsStrict(); + + if (!typesOnly) { + builder.Implementation(new TTestDateTime2Convert); + } + + return true; + } + +private: + NYql::NUdf::TUnboxedValue Run(const NYql::NUdf::IValueBuilder*, + const NYql::NUdf::TUnboxedValuePod* args) + const override { + NYql::NUdf::TUnboxedValuePod result(0); + auto& arg = Reference<TMResourceName>(args[0]); + auto& storage = Reference<TM64ResourceName>(result); + storage.From(arg); + return result; + } +}; + +class TTestDateTime2Split : public NYql::NUdf::TBoxedValue { +public: + explicit TTestDateTime2Split(NYql::NUdf::TSourcePosition pos) + : Pos_(pos) + {} + + static const NYql::NUdf::TStringRef& Name() { + static auto name = NYql::NUdf::TStringRef::Of("Split"); + return name; + } + + static bool DeclareSignature(const NYql::NUdf::TStringRef& name, + NYql::NUdf::TType*, + NYql::NUdf::IFunctionTypeInfoBuilder& builder, + bool typesOnly) + { + if (Name() != name) { + return false; + } + builder.SimpleSignature<NYql::NUdf::TResource<TMResourceName>(NYql::NUdf::TAutoMap<NYql::NUdf::TDatetime>)>() + .IsStrict(); + if (!typesOnly) { + builder.Implementation(new TTestDateTime2Split(builder.GetSourcePosition())); + } + + return true; + } + +private: + NYql::NUdf::TUnboxedValue Run(const NYql::NUdf::IValueBuilder* valueBuilder, + const NYql::NUdf::TUnboxedValuePod* args) + const override try { + EMPTY_RESULT_ON_EMPTY_ARG(0); + + auto& builder = valueBuilder->GetDateBuilder(); + NYql::NUdf::TUnboxedValuePod result(0); + auto& storage = Reference<TMResourceName>(result); + storage.FromDatetime(builder, args[0].Get<ui32>()); + return result; + } catch (const std::exception& e) { + UdfTerminate((TStringBuilder() << Pos_ << " " << e.what()).data()); + } + + const NYql::NUdf::TSourcePosition Pos_; +}; + +// Here are the short description for the versions: +// * A is an old version with DateTime::Format function using +// runconfig signature. +// * B is a new version with DateTime::Format function using +// currying signature with basic DateTime resource type for +// closure argument. +// * C is a new version with DateTime::Format function using +// currying signature with extended DateTime resource type +// for closure argument. +SIMPLE_MODULE(TTestADateTime2Module, TTestDateTime2Format<EBuilds::A, true>, + TTestDateTime2Split) +SIMPLE_MODULE(TTestB1DateTime2Module, TTestDateTime2Format<EBuilds::B, true>, + TTestDateTime2Convert<true>, + TTestDateTime2Split) +SIMPLE_MODULE(TTestB2DateTime2Module, TTestDateTime2Format<EBuilds::B, false>, + TTestDateTime2Convert<false>, + TTestDateTime2Split) +SIMPLE_MODULE(TTestC1DateTime2Module, TTestDateTime2Format<EBuilds::C, true>, + TTestDateTime2Convert<true>, + TTestDateTime2Split) +SIMPLE_MODULE(TTestC2DateTime2Module, TTestDateTime2Format<EBuilds::C, false>, + TTestDateTime2Convert<false>, + TTestDateTime2Split) + +template<bool LLVM, class TCompileModule, class TRunModule> +static void TestDateTimeFormat() { + // Create the test setup, using compileModule implementation + // for DateTime2 UDF. + TVector<TUdfModuleInfo> modules; + modules.emplace_back(TUdfModuleInfo{"", "DateTime2", new TCompileModule()}); + TSetup<LLVM> compileSetup(GetTestFactory(), std::move(modules)); + TProgramBuilder& pb = *compileSetup.PgmBuilder; + + // Build the graph, using the compileModule setup. + const auto dttype = pb.NewDataType(NUdf::EDataSlot::Datetime); + const auto dtnode = NewDateTimeNode("2009-09-01T15:37:19Z", *compileSetup.Env); + const auto format = pb.NewDataLiteral<NUdf::EDataSlot::String>("Canary is alive"); + + // Build the runtime node for formatter (i.e. DateTime2.Format + // resulting closure), considering its declared signature. + TRuntimeNode formatter; + if constexpr (std::is_same_v<TCompileModule, TTestADateTime2Module> || + std::is_same_v<TCompileModule, TTestB1DateTime2Module> || + std::is_same_v<TCompileModule, TTestC1DateTime2Module>) + { + formatter = pb.Udf("DateTime2.Format", format); + } else { + formatter = pb.Apply(pb.Udf("DateTime2.Format"), {format}); + } + + const auto list = pb.NewList(dttype, {dtnode}); + const auto pgmReturn = pb.Map(list, [&pb, formatter](const TRuntimeNode item) { + auto resource = pb.Apply(pb.Udf("DateTime2.Split"), {item}); + if constexpr (std::is_same_v<TCompileModule, TTestC2DateTime2Module>) { + resource = pb.Apply(pb.Udf("DateTime2.Convert"), {resource}); + } + return pb.Apply(formatter, {resource}); + }); + + // Create the test setup, using runModule implementation for + // DateTime2 UDF. + TVector<TUdfModuleInfo> runModules; + runModules.emplace_back(TUdfModuleInfo{"", "DateTime2", new TRunModule()}); + TSetup<LLVM> runSetup(GetTestFactory(), std::move(runModules)); + + // Move the graph from the one setup to another as a + // serialized bytecode sequence. + const auto bytecode = SerializeRuntimeNode(pgmReturn, *compileSetup.Env); + const auto root = DeserializeRuntimeNode(bytecode, *runSetup.Env); + + // Run the graph, using the runModule setup. + const auto graph = runSetup.BuildGraph(root); + const auto iterator = graph->GetValue().GetListIterator(); + + NUdf::TUnboxedValue result; + UNIT_ASSERT(iterator.Next(result)); + UNIT_ASSERT_STRINGS_EQUAL(TStringBuf(result.AsStringRef()), "Canary is alive: 1/9/2009 15:37:19."); + UNIT_ASSERT(!iterator.Next(result)); +} + +} // namespace + +// The main idea for the test below: check whether all hacks, +// introduced to the core components and DateTime UDF (that is +// partially stubbed above) works fine for the following +// "compile/execute" matrix: +// +-------------------------------------------+ +// | compile \ execute | A | B1 | C1 | B2 | C2 | +// +-------------------+---+----+----+----+----+ +// | A | + | + | + | - | - | +// +-------------------+---+----+----+----+----+ +// | B1 | + | + | + | + | - | +// +-------------------+---+----+----+----+----+ +// | C1 | + | + | + | - | + | +// +-------------------+---+----+----+----+----+ +// | B2 | - | + | - | + | - | +// +-------------------+---+----+----+----+----+ +// | C2 | - | - | + | - | + | +// +-------------------+---+----+----+----+----+ +Y_UNIT_TEST_SUITE(TMiniKQLDatetimeFormatTest) { + Y_UNIT_TEST_LLVM(AtoB1) { + TestDateTimeFormat<LLVM, TTestADateTime2Module, TTestB1DateTime2Module>(); + } + Y_UNIT_TEST_LLVM(B1toA) { + TestDateTimeFormat<LLVM, TTestB1DateTime2Module, TTestADateTime2Module>(); + } + Y_UNIT_TEST_LLVM(B1toB2) { + TestDateTimeFormat<LLVM, TTestB1DateTime2Module, TTestB2DateTime2Module>(); + } + Y_UNIT_TEST_LLVM(B2toB1) { + TestDateTimeFormat<LLVM, TTestB2DateTime2Module, TTestB1DateTime2Module>(); + } + Y_UNIT_TEST_LLVM(AtoC1) { + TestDateTimeFormat<LLVM, TTestADateTime2Module, TTestC1DateTime2Module>(); + } + Y_UNIT_TEST_LLVM(C1toA) { + TestDateTimeFormat<LLVM, TTestC1DateTime2Module, TTestADateTime2Module>(); + } + Y_UNIT_TEST_LLVM(B1toC1) { + TestDateTimeFormat<LLVM, TTestB1DateTime2Module, TTestC1DateTime2Module>(); + } + Y_UNIT_TEST_LLVM(C1toB1) { + TestDateTimeFormat<LLVM, TTestC1DateTime2Module, TTestB1DateTime2Module>(); + } + Y_UNIT_TEST_LLVM(C1toC2) { + TestDateTimeFormat<LLVM, TTestC1DateTime2Module, TTestC2DateTime2Module>(); + } + Y_UNIT_TEST_LLVM(C2toC1) { + TestDateTimeFormat<LLVM, TTestC2DateTime2Module, TTestC1DateTime2Module>(); + } +} // Y_UNIT_TEST_SUITE + } // namespace NMiniKQL } // namespace NKikimr |