summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorimunkin <[email protected]>2025-06-18 11:27:02 +0300
committerimunkin <[email protected]>2025-06-18 13:27:13 +0300
commit06674e69d9005bafa2ac27df970398fd1c389fdf (patch)
treeaf1d06bf39a7988e3439580940b9c540c3e40dc4
parentf2030b6e6ffca86485107e3c699f01a555bcf45b (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.cpp183
-rw-r--r--yql/essentials/minikql/comp_nodes/ut/mkql_udf_ut.cpp371
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