diff --git a/ydb/library/yql/providers/s3/path_generator/ut/yql_generate_partitioning_rules_ut.cpp b/ydb/library/yql/providers/s3/path_generator/ut/yql_generate_partitioning_rules_ut.cpp index c6070217ea34..4d55019d2338 100644 --- a/ydb/library/yql/providers/s3/path_generator/ut/yql_generate_partitioning_rules_ut.cpp +++ b/ydb/library/yql/providers/s3/path_generator/ut/yql_generate_partitioning_rules_ut.cpp @@ -318,6 +318,38 @@ Y_UNIT_TEST_SUITE(TGenerateTests) { } )", {"year"}), yexception, "Location path 1971/ is composed by different projection value sets { ${year} = 1971-01-01 } and { ${year} = 1971-01-02 }");; } + + Y_UNIT_TEST(SuccessGenerateDateWithNegativeNow) { + auto generator = CreatePathGenerator(R"( + { + "projection.enabled" : true, + "projection.city.type" : "enum", + "projection.city.values" : "MSK,SPB", + "projection.code.type" : "date", + "projection.code.min" : "NOW - 3 DAYS", + "projection.code.max" : "NOW", + "projection.code.format" : "%F", + "projection.code.interval" : 1, + "projection.code.unit" : "DAYS", + "storage.location.template" : "/${city}/${code}/" + } + )", {"city", "code"}); + auto nowBefore = TInstant::Now(); + auto rules = generator->GetRules(); + auto nowAfter = TInstant::Now(); + + TSet items; + for (auto from = std::min(nowBefore, nowAfter) - TDuration::Days(3); from <= std::max(nowBefore, nowAfter); from += TDuration::Days(1)) { + items.insert("MSK/" + from.FormatLocalTime("%F") + "/"); + items.insert("SPB/" + from.FormatLocalTime("%F") + "/"); + } + + UNIT_ASSERT_VALUES_EQUAL(rules.size(), 8); + for (const auto& rule: rules) { + UNIT_ASSERT(items.contains(rule.Path)); + UNIT_ASSERT_VALUES_EQUAL(rule.ColumnValues.size(), 2); + } + } } } diff --git a/ydb/library/yql/providers/s3/path_generator/yql_s3_path_generator.cpp b/ydb/library/yql/providers/s3/path_generator/yql_s3_path_generator.cpp index 5e5d92fd8fae..f400cb4bcedf 100644 --- a/ydb/library/yql/providers/s3/path_generator/yql_s3_path_generator.cpp +++ b/ydb/library/yql/providers/s3/path_generator/yql_s3_path_generator.cpp @@ -157,6 +157,20 @@ bool IsOverflow(ui64 a, ui64 b) { return b > diff; } +ui64 AbsToUi64(i64 value) { + if (value >= 0) { + return value; + } + if (value == std::numeric_limits::min()) { + return (ui64)std::numeric_limits::max() + 1; + } + return -value; +} + +bool IsOverflow(ui64 a, i64 b) { + return b > 0 ? IsOverflow(a, (ui64)b) : a < AbsToUi64(b); +} + TDuration FromUnit(int64_t interval, IPathGenerator::EIntervalUnit unit) { switch (unit) { case IPathGenerator::EIntervalUnit::MILLISECONDS: @@ -203,13 +217,17 @@ TInstant AddUnit(TInstant current, int64_t interval, IPathGenerator::EIntervalUn return DoAddYears(current, interval); } + const TDuration delta = FromUnit(abs(interval), unit); + if (delta.GetValue() > std::numeric_limits::max()) { + ythrow yexception() << "Interval is overflowed"; + } - const TDuration delta = FromUnit(interval, unit); - if (IsOverflow(current.GetValue(), delta.GetValue())) { + const i64 deltaValue = (interval > 0 ? 1LL : -1LL) * delta.GetValue(); + if (IsOverflow(current.GetValue(), deltaValue)) { ythrow yexception() << "Timestamp is overflowed"; } - return current + delta; + return interval > 0 ? current + delta : current - delta; } TInstant ParseDate(const TString& dateStr, const TInstant& now) {