summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreakozyrev <[email protected]>2026-07-07 12:40:35 +0300
committereakozyrev <[email protected]>2026-07-07 13:17:41 +0300
commitdba709c362d120dbcaf83af9d7eebe7a3dd95f71 (patch)
tree96fe693dcf89adbf2dc947cc5467eee4e332fc2e
parentd70e855305304f943a338b17d9893f9699310682 (diff)
YQL-21215: Finite values checks ListFromRange
1. Добавил проверку start/end на равенство бесконечности -\> возвращается пустой список 2. Изменил логику инкремента индекса (перевел на целочисленный тип), чтобы работало валидно при малых значениях float commit_hash:f6b473a1d0227269591f9a683d81614696f0b040
-rw-r--r--yql/essentials/docs/en/builtins/list.md1
-rw-r--r--yql/essentials/docs/ru/builtins/list.md1
-rw-r--r--yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp152
-rw-r--r--yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp63
4 files changed, 144 insertions, 73 deletions
diff --git a/yql/essentials/docs/en/builtins/list.md b/yql/essentials/docs/en/builtins/list.md
index fffa60aaf9c..ef125e4f375 100644
--- a/yql/essentials/docs/en/builtins/list.md
+++ b/yql/essentials/docs/en/builtins/list.md
@@ -450,6 +450,7 @@ Specifics:
* If the step is neither positive nor negative (0 or NaN), the result list is empty.
* If any of the parameters is optional, the result list is optional.
* If any of the parameters is `NULL`, the result is `NULL`.
+* If any of the parameters is `double/float("inf")`, the result list is empty.
#### Examples
diff --git a/yql/essentials/docs/ru/builtins/list.md b/yql/essentials/docs/ru/builtins/list.md
index aad2f4e3a32..fbb50573a42 100644
--- a/yql/essentials/docs/ru/builtins/list.md
+++ b/yql/essentials/docs/ru/builtins/list.md
@@ -718,6 +718,7 @@ ListFold1Map(List<T>?, (T)->Tuple<U,S>, (T, S)->Tuple<U,S>)->List<U>?
* Если шаг не положительный и не отрицательный (0 или NaN), то список будет пустой.
* Если один из параметров опциональный, то результат будет опциональный список.
* Если один из параметров равен `NULL`, то результат будет `NULL`.
+* Если один из параметров равен `double/float("inf")`, то список будет пустой.
#### Примеры
diff --git a/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp b/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp
index 5a4c8fef6b3..91d673a4946 100644
--- a/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp
+++ b/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp
@@ -1,6 +1,7 @@
#include "mkql_listfromrange.h"
#include <yql/essentials/minikql/computation/mkql_computation_node_holders.h>
#include <yql/essentials/minikql/computation/mkql_computation_node_codegen.h> // Y_IGNORE
+#include <yql/essentials/minikql/computation/mkql_custom_list.h>
#include <yql/essentials/minikql/mkql_node_cast.h>
#include <yql/essentials/minikql/mkql_safe_arithmetic_ops.h>
@@ -57,11 +58,7 @@ private:
class TValue: public TComputationValue<TValue> {
public:
- template <bool Asc, bool Float>
- class TIterator;
-
- template <bool Asc>
- class TIterator<Asc, false>: public TComputationValue<TIterator<Asc, false>> {
+ class TIterator: public TComputationValue<TIterator> {
public:
TIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step)
: TComputationValue<TIterator>(memInfo)
@@ -101,43 +98,6 @@ private:
}
};
- template <bool Asc>
- class TIterator<Asc, true>: public TComputationValue<TIterator<Asc, true>> {
- public:
- TIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step)
- : TComputationValue<TIterator>(memInfo)
- , Start(start)
- , Index(-T(1))
- , Limit(end - start)
- , Step(step)
- {
- }
-
- private:
- bool Skip() final {
- const auto next = Index + T(1);
- if (Asc ? next * Step < Limit : next * Step > Limit) {
- Index = next;
- return true;
- }
-
- return false;
- }
-
- bool Next(NUdf::TUnboxedValue& value) final {
- if (!Skip()) {
- return false;
- }
-
- value = NUdf::TUnboxedValuePod(Start + Index * Step);
- return true;
- }
-
- const T Start;
- T Index;
- const T Limit;
- const TStep Step;
- };
TValue(TMemoryUsageInfo* memInfo, TComputationContext& ctx, T start, T end, TStep step)
: TComputationValue<TValue>(memInfo)
, Ctx(ctx)
@@ -149,35 +109,16 @@ private:
protected:
NUdf::TUnboxedValue GetListIterator() const override {
- if (Step > TStep(0)) {
- return Ctx.HolderFactory.template Create<TIterator<true, std::is_floating_point<T>::value>>(Start, End, Step);
- } else if (Step < TStep(0)) {
- return Ctx.HolderFactory.template Create<TIterator<false, std::is_floating_point<T>::value>>(Start, End, Step);
+ if (Step != TStep(0)) {
+ return Ctx.HolderFactory.template Create<TIterator>(Start, End, Step);
} else {
return Ctx.HolderFactory.GetEmptyContainerLazy();
}
}
ui64 GetListLength() const final {
- if constexpr (std::is_integral_v<T>) {
- return GetElementsCount<T, TStep>(Start, End, Step);
- }
-
- if (Step > T(0) && Start < End) {
- ui64 len = 0ULL;
- for (T i = 0; i * Step < End - Start; i += T(1)) {
- ++len;
- }
- return len;
- } else if (Step < T(0) && Start > End) {
- ui64 len = 0ULL;
- for (T i = 0; i * Step > End - Start; i += T(1)) {
- ++len;
- }
- return len;
- } else {
- return 0ULL;
- }
+ static_assert(std::is_integral_v<T>, "Invalid type");
+ return GetElementsCount<T, TStep>(Start, End, Step);
}
bool HasListItems() const final {
@@ -191,7 +132,73 @@ private:
}
bool HasFastListLength() const final {
- return std::is_integral<T>();
+ return true;
+ }
+
+ TComputationContext& Ctx;
+ const T Start;
+ const T End;
+ const TStep Step;
+ };
+
+ class TFloatingValue: public TCustomListValue {
+ static constexpr ui64 MaxElementsCount = std::numeric_limits<ui64>::max();
+
+ public:
+ class TIterator: public TComputationValue<TIterator> {
+ public:
+ TIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step)
+ : TComputationValue<TIterator>(memInfo)
+ , Start(start)
+ , End(end)
+ , Step(step)
+ , ValidInput(CheckInput(start, end, step))
+ , Index(0ULL)
+ {
+ }
+
+ private:
+ bool Next(NUdf::TUnboxedValue& value) final {
+ if (!ValidInput || Index >= MaxElementsCount) {
+ return false;
+ }
+ const T current = Start + Index * Step;
+ const bool outOfRange = Step > 0 ? current >= End : current <= End;
+ if (outOfRange) {
+ return false;
+ }
+ value = NUdf::TUnboxedValuePod(current);
+ Index++;
+ return true;
+ }
+
+ static bool CheckInput(T start, T end, TStep step) {
+ if (step == T(0) || std::isnan(step) || std::isnan(start) || std::isnan(end) ||
+ std::isinf(start) || std::isinf(end) || std::isinf(step) || start == end) {
+ return false;
+ }
+
+ return true;
+ }
+
+ const T Start;
+ const T End;
+ const TStep Step;
+ const bool ValidInput;
+ ui64 Index;
+ };
+ TFloatingValue(TMemoryUsageInfo* memInfo, TComputationContext& ctx, T start, T end, TStep step)
+ : TCustomListValue(memInfo)
+ , Ctx(ctx)
+ , Start(start)
+ , End(end)
+ , Step(step)
+ {
+ }
+
+ protected:
+ NUdf::TUnboxedValue GetListIterator() const override {
+ return Ctx.HolderFactory.template Create<TIterator>(Start, End, Step);
}
TComputationContext& Ctx;
@@ -202,10 +209,9 @@ private:
class TTzValue: public TValue {
public:
- template <bool Asc>
- class TTzIterator: public TValue::template TIterator<Asc, false> {
+ class TTzIterator: public TValue::TIterator {
public:
- using TBase = typename TValue::template TIterator<Asc, false>;
+ using TBase = typename TValue::TIterator;
TTzIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step, ui16 Tz)
: TBase(memInfo, start, end, step)
, TimezoneId(Tz)
@@ -223,10 +229,8 @@ private:
const ui16 TimezoneId;
};
NUdf::TUnboxedValue GetListIterator() const final {
- if (TValue::Step > TStep(0)) {
- return TValue::Ctx.HolderFactory.template Create<TTzIterator<true>>(TValue::Start, TValue::End, TValue::Step, TimezoneId);
- } else if (TValue::Step < TStep(0)) {
- return TValue::Ctx.HolderFactory.template Create<TTzIterator<false>>(TValue::Start, TValue::End, TValue::Step, TimezoneId);
+ if (TValue::Step != TStep(0)) {
+ return TValue::Ctx.HolderFactory.template Create<TTzIterator>(TValue::Start, TValue::End, TValue::Step, TimezoneId);
} else {
return TValue::Ctx.HolderFactory.GetEmptyContainerLazy();
}
@@ -299,6 +303,8 @@ private:
static NUdf::TUnboxedValuePod MakeList(TComputationContext& ctx, T start, T end, TStep step, ui16 timezoneId) {
if constexpr (TzDate) {
return ctx.HolderFactory.Create<TTzValue>(ctx, start, end, step, timezoneId);
+ } else if constexpr (std::is_floating_point_v<T>) {
+ return ctx.HolderFactory.Create<TFloatingValue>(ctx, start, end, step);
} else {
return ctx.HolderFactory.Create<TValue>(ctx, start, end, step);
}
diff --git a/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp b/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp
index 7f6a60456b0..9ebe93d3aeb 100644
--- a/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp
+++ b/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp
@@ -391,6 +391,69 @@ Y_UNIT_TEST_LLVM(TestResverseUnsignedShorts) {
const auto list = graph->GetValue();
AssertUnboxedValueElementEqual(list, TVector<ui16>{60000U, 59998U, 59996U, 59994U, 59992U});
}
+
+namespace {
+template <bool UseLLVM>
+void TestFloatStep(TSetup<UseLLVM>& setup, float startVal, float endVal, float stepVal, ui64 expectedLength) {
+ TProgramBuilder& pb = *setup.PgmBuilder;
+
+ const auto start = NTest::ConvertValueToLiteralNode(pb, startVal);
+ const auto end = NTest::ConvertValueToLiteralNode(pb, endVal);
+ const auto step = NTest::ConvertValueToLiteralNode(pb, stepVal);
+
+ const auto range = pb.ListFromRange(start, end, step);
+
+ const auto graph = setup.BuildGraph(range);
+ const auto list = graph->GetValue();
+ UNIT_ASSERT_VALUES_EQUAL(list.GetListLength(), expectedLength);
+
+ TVector<float> vector(expectedLength);
+ for (size_t i = 0; i < expectedLength; ++i) {
+ vector[i] = startVal + i * stepVal;
+ }
+ AssertUnboxedValueElementEqual(list, vector);
+}
+} // namespace
+
+Y_UNIT_TEST_LLVM(TestFloatSmallStepNoInfiniteLoop) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, 1.0f, 2.0f, 1e-6f, 1000000);
+}
+
+Y_UNIT_TEST_LLVM(TestFloatSmallNegativeStepNoInfiniteLoop) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, 2.0f, 1.0f, -1e-6f, 1000000);
+}
+
+Y_UNIT_TEST_LLVM(TestFloatNegativeStep) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, 1.0f, 2.0f, -1e-8f, 0);
+}
+
+Y_UNIT_TEST_LLVM(TestFloatWithInfinityEnd) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, 0.0f, INFINITY, 1.0f, 0);
+}
+Y_UNIT_TEST_LLVM(TestFloatWithNegativeInfinityStart) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, -INFINITY, 2.0f, 1.0f, 0);
+}
+
+Y_UNIT_TEST_LLVM(TestFloatWithInfinityStep) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, 1.0f, 2.0f, INFINITY, 0);
+}
+
+Y_UNIT_TEST_LLVM(TestFloatWithExtraLargeStep) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, -0.0000000000000000000000000000000116082984f, 0.0000000000000000096245773f, 6925700880000000000000000000000.0f, 1);
+}
+
+Y_UNIT_TEST_LLVM(TestFloatWithLargeNegativeStep) {
+ TSetup<LLVM> setup;
+ TestFloatStep(setup, -0.000000000346562223f, -277088.812f, -23368489200000.0f, 1);
+}
+
} // Y_UNIT_TEST_SUITE(TMiniKQLListFromRangeTest)
} // namespace NMiniKQL
} // namespace NKikimr