#include "mkql_block_way.h" #include #include #include #include #include #include #include #include #include namespace NKikimr::NMiniKQL { namespace { template class TWayBlockExecBase { public: explicit TWayBlockExecBase(TType* resultItemType) : ResultItemType_(resultItemType) { } virtual ~TWayBlockExecBase() = default; arrow::Status Exec(arrow::compute::KernelContext* ctx, const arrow::compute::ExecBatch& batch, arrow::Datum* res) const { const arrow::Datum& variantDatum = batch.values[0]; if (variantDatum.is_scalar()) { *res = ConvertScalar(ResultItemType_, ComputeScalarKey(*variantDatum.scalar()), *ctx->memory_pool()); return arrow::Status::OK(); } MKQL_ENSURE(variantDatum.is_array(), "Expected array datum"); const arrow::ArrayData& inputArray = *variantDatum.array(); const i8* typeCodes = GetUnionArray(inputArray).GetValues(1); return ExecArray(ctx, inputArray, typeCodes, res); } protected: static const arrow::ArrayData& GetUnionArray(const arrow::ArrayData& inputArray) { if constexpr (IsOptional) { return *inputArray.child_data[0]; } else { return inputArray; } } private: virtual NUdf::TBlockItem MakeKey(ui32 alternativeIndex) const = 0; virtual arrow::Status ExecArray(arrow::compute::KernelContext* ctx, const arrow::ArrayData& inputArray, const i8* typeCodes, arrow::Datum* res) const = 0; NUdf::TBlockItem ComputeScalarKey(const arrow::Scalar& scalar) const { if constexpr (IsOptional) { if (!scalar.is_valid) { return NUdf::TBlockItem{}; } const auto& structScalar = arrow::internal::checked_cast(scalar); const auto& unionScalar = arrow::internal::checked_cast(*structScalar.value.front()); return MakeKey(unionScalar.Index); } else { const auto& unionScalar = arrow::internal::checked_cast(scalar); return MakeKey(unionScalar.Index); } } TType* const ResultItemType_; }; template class TWayBlockExecTuple final: public TWayBlockExecBase { using TBase = TWayBlockExecBase; public: using TBase::TBase; private: NUdf::TBlockItem MakeKey(ui32 alternativeIndex) const final { return NUdf::TBlockItem(alternativeIndex); } arrow::Status ExecArray(arrow::compute::KernelContext* ctx, const arrow::ArrayData& inputArray, const i8* typeCodes, arrow::Datum* res) const final { auto* pool = ctx->memory_pool(); const i64 length = inputArray.length; std::shared_ptr values = ARROW_RESULT(arrow::AllocateBuffer(length * sizeof(ui32), pool)); ui32* outValues = reinterpret_cast(values->mutable_data()); for (i64 i = 0; i < length; ++i) { outValues[i] = static_cast(typeCodes[i]); } std::shared_ptr mask; if (IsOptional && inputArray.buffers[0]) { mask = MakeDenseBitmapCopyIfOffsetDiffers(inputArray.buffers[0], length, inputArray.offset, 0, pool); } *res = arrow::ArrayData::Make(arrow::uint32(), length, {std::move(mask), std::move(values)}); return arrow::Status::OK(); } }; template class TWayBlockExecStruct final: public TWayBlockExecBase { using TBase = TWayBlockExecBase; public: TWayBlockExecStruct(TType* resultItemType, TVector&& alternativeNames) : TBase(resultItemType) , AlternativeNames_(std::move(alternativeNames)) { } private: NUdf::TBlockItem MakeKey(ui32 alternativeIndex) const final { return NUdf::TBlockItem(NUdf::TStringRef(AlternativeNames_[alternativeIndex])); } arrow::Status ExecArray(arrow::compute::KernelContext* ctx, const arrow::ArrayData& inputArray, const i8* typeCodes, arrow::Datum* res) const final { auto* pool = ctx->memory_pool(); arrow::Datum built = BuildNameArray(pool, inputArray, typeCodes); const ui8* maskBits = (IsOptional && inputArray.buffers[0]) ? inputArray.buffers[0]->data() : nullptr; *res = maskBits ? ReattachNullMask(pool, built, inputArray, maskBits) : std::move(built); return arrow::Status::OK(); } arrow::Datum BuildNameArray(arrow::MemoryPool* pool, const arrow::ArrayData& inputArray, const i8* typeCodes) const { const i64 length = inputArray.length; NYql::NUdf::TStringArrayBuilder builder( TTypeInfoHelper(), arrow::utf8(), *pool, static_cast(length)); for (i64 i = 0; i < length; ++i) { const bool isNull = IsOptional && NYql::NUdf::IsNull(inputArray, static_cast(i)); const NUdf::TStringRef name = isNull ? NUdf::TStringRef("", 0) : NUdf::TStringRef(AlternativeNames_[static_cast(typeCodes[i])]); builder.Add(NUdf::TBlockItem(name)); } return builder.Build(true); } static arrow::Datum ReattachNullMask(arrow::MemoryPool* pool, const arrow::Datum& built, const arrow::ArrayData& inputArray, const ui8* maskBits) { TVector> chunks; i64 processed = 0; ForEachArrayData(built, [&](const std::shared_ptr& chunk) { auto withMask = chunk->Copy(); MKQL_ENSURE(chunk->offset == 0, "Expected offset after string builder to be 0"); auto chunkMask = AllocateBitmapWithReserve(chunk->length, pool); arrow::internal::CopyBitmap(maskBits, inputArray.offset + processed, chunk->length, chunkMask->mutable_data(), 0); withMask->buffers[0] = std::move(chunkMask); withMask->SetNullCount(arrow::kUnknownNullCount); processed += chunk->length; chunks.push_back(std::move(withMask)); }); return MakeArray(chunks); } const TVector AlternativeNames_; }; template std::shared_ptr MakeBlockWayKernel(const TVector& argTypes, TType* resultType, std::shared_ptr> exec) { auto kernel = std::make_shared( ConvertToInputTypes(argTypes), ConvertToOutputType(resultType), [exec](arrow::compute::KernelContext* ctx, const arrow::compute::ExecBatch& batch, arrow::Datum* res) { return exec->Exec(ctx, batch, res); }); kernel->null_handling = arrow::compute::NullHandling::COMPUTED_NO_PREALLOCATE; kernel->mem_allocation = arrow::compute::MemAllocation::NO_PREALLOCATE; return kernel; } TVector CollectStructAlternativeNames(TStructType* structType) { TVector names; names.reserve(structType->GetMembersCount()); for (ui32 i = 0; i < structType->GetMembersCount(); ++i) { names.emplace_back(structType->GetMemberName(i)); } return names; } template std::shared_ptr MakeBlockWayKernel(const TVector& argTypes, TType* resultType, TType* underlyingType) { TType* resultItemType = AS_TYPE(TBlockType, resultType)->GetItemType(); std::shared_ptr> exec; if (underlyingType->IsTuple()) { exec = std::make_shared>(resultItemType); } else { exec = std::make_shared>( resultItemType, CollectStructAlternativeNames(AS_TYPE(TStructType, underlyingType))); } return MakeBlockWayKernel(argTypes, resultType, std::move(exec)); } } // namespace IComputationNode* WrapBlockWay(TCallable& callable, const TComputationNodeFactoryContext& ctx) { MKQL_ENSURE(callable.GetInputsCount() == 1, "Expected 1 argument"); auto blockType = AS_TYPE(TBlockType, callable.GetInput(0).GetStaticType()); bool isOptional; auto variantItemType = UnpackOptional(blockType->GetItemType(), isOptional); auto underlyingType = AS_TYPE(TVariantType, variantItemType)->GetUnderlyingType(); TVector argsTypes = {blockType}; auto resultType = callable.GetType()->GetReturnType(); auto kernel = isOptional ? MakeBlockWayKernel(argsTypes, resultType, underlyingType) : MakeBlockWayKernel(argsTypes, resultType, underlyingType); TComputationNodePtrVector argsNodes = {LocateNode(ctx.NodeLocator, callable, 0)}; return new TBlockFuncNode(ctx.Mutables, ctx.RuntimeSettings->DatumValidation.Get(), callable.GetType()->GetName(), std::move(argsNodes), argsTypes, resultType, *kernel, kernel); } } // namespace NKikimr::NMiniKQL