diff --git a/ydb/core/tx/schemeshard/common/validation.cpp b/ydb/core/tx/schemeshard/common/validation.cpp index 2ef0e4f89a37..1e52ec848195 100644 --- a/ydb/core/tx/schemeshard/common/validation.cpp +++ b/ydb/core/tx/schemeshard/common/validation.cpp @@ -1,5 +1,6 @@ #include "validation.h" +#include #include extern "C" { @@ -59,10 +60,30 @@ bool TTTLValidator::ValidateUnit(const NScheme::TTypeInfo columnType, NKikimrSch bool TTTLValidator::ValidateTiers(const NKikimrSchemeOp::TTTLSettings::TEnabled ttlSettings, TString& errStr) { for (ui64 i = 0; i < ttlSettings.TiersSize(); ++i) { - if (ttlSettings.GetTiers(i).HasDelete() && i + 1 != ttlSettings.TiersSize()) { - errStr = "Only the last tier in TTL settings can have Delete action"; + const auto& tier = ttlSettings.GetTiers(i); + if (!tier.HasApplyAfterSeconds()) { + errStr = TStringBuilder() << "Tier " << i << ": missing ApplyAfterSeconds"; return false; } + if (i != 0 && tier.GetApplyAfterSeconds() <= ttlSettings.GetTiers(i - 1).GetApplyAfterSeconds()) { + errStr = TStringBuilder() << "Tiers in the sequence must have increasing ApplyAfterSeconds: " + << ttlSettings.GetTiers(i - 1).GetApplyAfterSeconds() << " (tier " << i - 1 + << ") >= " << tier.GetApplyAfterSeconds() << " (tier " << i << ")"; + return false; + } + switch (tier.GetActionCase()) { + case NKikimrSchemeOp::TTTLSettings_TTier::kDelete: + if (i + 1 != ttlSettings.TiersSize()) { + errStr = TStringBuilder() << "Tier " << i << ": only the last tier in TTL settings can have Delete action"; + return false; + } + break; + case NKikimrSchemeOp::TTTLSettings_TTier::kEvictToExternalStorage: + break; + case NKikimrSchemeOp::TTTLSettings_TTier::ACTION_NOT_SET: + errStr = TStringBuilder() << "Tier " << i << ": missing Action"; + return false; + } } return true; } diff --git a/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp index 7c221db5c879..dffd197dba36 100644 --- a/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp +++ b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl.cpp @@ -1207,7 +1207,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardTTLTests) { } } } - )", {{NKikimrScheme::StatusInvalidParameter, "Only the last tier in TTL settings can have Delete action"}}); + )", {{NKikimrScheme::StatusInvalidParameter, "Tier 0: only the last tier in TTL settings can have Delete action"}}); TestAlterTable(runtime, ++txId, "/MyRoot", R"( Name: "TTLEnabledTable" diff --git a/ydb/core/tx/schemeshard/ut_ttl/ut_ttl_utility.cpp b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl_utility.cpp new file mode 100644 index 000000000000..ba658e23fef5 --- /dev/null +++ b/ydb/core/tx/schemeshard/ut_ttl/ut_ttl_utility.cpp @@ -0,0 +1,100 @@ +#include +#include + +#include + +using namespace NKikimr; +using namespace NSchemeShard; + +Y_UNIT_TEST_SUITE(TSchemeShardTTLUtility) { + void TestValidateTiers(const std::vector& tiers, const TConclusionStatus& expectedResult) { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + for (const auto& tier : tiers) { + *input.AddTiers() = tier; + } + + TString error; + UNIT_ASSERT_VALUES_EQUAL(NValidation::TTTLValidator::ValidateTiers(input, error), expectedResult.IsSuccess()); + if (expectedResult.IsFail()) { + UNIT_ASSERT_STRING_CONTAINS(error, expectedResult.GetErrorMessage()); + } + } + + Y_UNIT_TEST(ValidateTiers) { + NKikimrSchemeOp::TTTLSettings::TTier tierNoAction; + tierNoAction.SetApplyAfterSeconds(60); + NKikimrSchemeOp::TTTLSettings::TTier tierNoDuration; + tierNoDuration.MutableDelete(); + auto makeDeleteTier = [](const ui32 seconds) { + NKikimrSchemeOp::TTTLSettings::TTier tier; + tier.MutableDelete(); + tier.SetApplyAfterSeconds(seconds); + return tier; + }; + auto makeEvictTier = [](const ui32 seconds) { + NKikimrSchemeOp::TTTLSettings::TTier tier; + tier.MutableEvictToExternalStorage()->SetStorageName("/Root/abc"); + tier.SetApplyAfterSeconds(seconds); + return tier; + }; + + TestValidateTiers({ tierNoAction }, TConclusionStatus::Fail("Tier 0: missing Action")); + TestValidateTiers({ tierNoDuration }, TConclusionStatus::Fail("Tier 0: missing ApplyAfterSeconds")); + TestValidateTiers({ makeDeleteTier(1) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1), makeDeleteTier(2) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1), makeEvictTier(2), makeDeleteTier(3) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(1), makeEvictTier(2) }, TConclusionStatus::Success()); + TestValidateTiers({ makeEvictTier(2), makeEvictTier(1) }, TConclusionStatus::Fail("Tiers in the sequence must have increasing ApplyAfterSeconds: 2 (tier 0) >= 1 (tier 1)")); + TestValidateTiers({ makeDeleteTier(1), makeEvictTier(2) }, TConclusionStatus::Fail("Tier 0: only the last tier in TTL settings can have Delete action")); + TestValidateTiers({ makeDeleteTier(1), makeDeleteTier(2) }, TConclusionStatus::Fail("Tier 0: only the last tier in TTL settings can have Delete action")); + } + + void ValidateGetExpireAfter(const NKikimrSchemeOp::TTTLSettings::TEnabled& ttlSettings, const bool allowNonDeleteTiers, const TConclusion& expectedResult) { + auto result = GetExpireAfter(ttlSettings, allowNonDeleteTiers); + UNIT_ASSERT_VALUES_EQUAL(result.IsSuccess(), expectedResult.IsSuccess()); + if (expectedResult.IsFail()) { + UNIT_ASSERT_STRING_CONTAINS(result.GetErrorMessage(), expectedResult.GetErrorMessage()); + } + } + + Y_UNIT_TEST(GetExpireAfter) { + NKikimrSchemeOp::TTTLSettings::TTier evictTier; + evictTier.MutableEvictToExternalStorage()->SetStorageName("/Root/abc"); + evictTier.SetApplyAfterSeconds(1800); + NKikimrSchemeOp::TTTLSettings::TTier deleteTier; + deleteTier.MutableDelete(); + deleteTier.SetApplyAfterSeconds(3600); + + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + ValidateGetExpireAfter(input, true, TDuration::Zero()); + ValidateGetExpireAfter(input, false, TDuration::Zero()); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + input.SetExpireAfterSeconds(60); + ValidateGetExpireAfter(input, true, TDuration::Seconds(60)); + ValidateGetExpireAfter(input, false, TDuration::Seconds(60)); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + *input.AddTiers() = deleteTier; + ValidateGetExpireAfter(input, true, TDuration::Seconds(3600)); + ValidateGetExpireAfter(input, false, TDuration::Seconds(3600)); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + *input.AddTiers() = evictTier; + *input.AddTiers() = deleteTier; + ValidateGetExpireAfter(input, true, TDuration::Seconds(3600)); + ValidateGetExpireAfter(input, false, TConclusionStatus::Fail("Only DELETE via TTL is allowed for row-oriented tables")); + } + { + NKikimrSchemeOp::TTTLSettings::TEnabled input; + *input.AddTiers() = evictTier; + ValidateGetExpireAfter(input, true, TConclusionStatus::Fail("TTL settings does not contain DELETE action")); + ValidateGetExpireAfter(input, false, TConclusionStatus::Fail("Only DELETE via TTL is allowed for row-oriented tables")); + } + } +} diff --git a/ydb/core/tx/schemeshard/ut_ttl/ya.make b/ydb/core/tx/schemeshard/ut_ttl/ya.make index 42a2bf28fc43..72fa34fae64f 100644 --- a/ydb/core/tx/schemeshard/ut_ttl/ya.make +++ b/ydb/core/tx/schemeshard/ut_ttl/ya.make @@ -22,6 +22,7 @@ PEERDIR( SRCS( ut_ttl.cpp + ut_ttl_utility.cpp ) YQL_LAST_ABI_VERSION()