diff options
author | udovichenko-r <[email protected]> | 2025-03-27 09:42:31 +0300 |
---|---|---|
committer | udovichenko-r <[email protected]> | 2025-03-27 09:56:18 +0300 |
commit | 86e72e8f45d1eb0948a9a20f82b3f81a03df77b5 (patch) | |
tree | d84cf7111d81a5aa191f7f308dccbe115b4ed3ee /library/cpp | |
parent | 54394ecb8d819f075dddd15ed8cdb4fb822d66cd (diff) |
Fix YT deserialization of schemas with Decimal columns having zero scale
commit_hash:04525d1d60a5ace05ce17e9c8aebdd3b4b750eef
Diffstat (limited to 'library/cpp')
-rw-r--r-- | library/cpp/type_info/type_io.cpp | 33 | ||||
-rw-r--r-- | library/cpp/type_info/ut/type_deserialize.cpp | 34 |
2 files changed, 56 insertions, 11 deletions
diff --git a/library/cpp/type_info/type_io.cpp b/library/cpp/type_info/type_io.cpp index 12f0058fed1..f397466abce 100644 --- a/library/cpp/type_info/type_io.cpp +++ b/library/cpp/type_info/type_io.cpp @@ -13,6 +13,8 @@ #include <util/generic/vector.h> #include <util/generic/scope.h> +#include <optional> + namespace NTi::NIo { namespace { class TYsonDeserializer: private TNonCopyable { @@ -61,7 +63,8 @@ namespace NTi::NIo { const TType *Key, *Value; }; struct TDecimalData { - ui8 Precision, Scale; + std::optional<ui8> Precision; + std::optional<ui8> Scale; }; using TTypeData = std::variant< std::monostate, @@ -162,11 +165,19 @@ namespace NTi::NIo { } } else if (mapKey == "precision") { if (std::holds_alternative<std::monostate>(data)) { - data = TDecimalData{ReadSmallInt(R"("precision")"), 0}; + const auto precision = ReadSmallInt(R"("precision")"); + if (0 == precision) { + ythrow TDeserializationException() << R"(invalid zero "precision")"; + } + data = TDecimalData{precision, std::nullopt}; } else if (std::holds_alternative<TDecimalData>(data)) { auto& decimalData = std::get<TDecimalData>(data); - if (decimalData.Precision == 0) { - decimalData.Precision = ReadSmallInt(R"("precision")"); + if (!decimalData.Precision.has_value()) { + const auto precision = ReadSmallInt(R"("precision")"); + if (0 == precision) { + ythrow TDeserializationException() << R"(invalid zero "precision")"; + } + decimalData.Precision = precision; } else { ythrow TDeserializationException() << R"(duplicate key "precision")"; } @@ -175,10 +186,10 @@ namespace NTi::NIo { } } else if (mapKey == "scale") { if (std::holds_alternative<std::monostate>(data)) { - data = TDecimalData{0, ReadSmallInt(R"("scale")")}; + data = TDecimalData{std::nullopt, ReadSmallInt(R"("scale")")}; } else if (std::holds_alternative<TDecimalData>(data)) { auto& decimalData = std::get<TDecimalData>(data); - if (decimalData.Scale == 0) { + if (!decimalData.Scale.has_value()) { decimalData.Scale = ReadSmallInt(R"("scale")"); } else { ythrow TDeserializationException() << R"(duplicate key "scale")"; @@ -272,15 +283,15 @@ namespace NTi::NIo { auto& decimalData = std::get<TDecimalData>(data); - if (decimalData.Precision == 0) { + if (!decimalData.Precision.has_value()) { ythrow TDeserializationException() << R"(missing required key "precision" for type Decimal)"; } - if (decimalData.Scale == 0) { + if (!decimalData.Scale.has_value()) { ythrow TDeserializationException() << R"(missing required key "scale" for type Decimal)"; } - return Factory_->DecimalRaw(decimalData.Precision, decimalData.Scale); + return Factory_->DecimalRaw(decimalData.Precision.value(), decimalData.Scale.value()); } case ETypeName::Json: type = TJsonType::InstanceRaw(); @@ -420,8 +431,8 @@ namespace NTi::NIo { auto result = event.AsScalar().AsInt64(); - if (result <= 0) { - ythrow TDeserializationException() << what << " must be greater than zero"; + if (result < 0) { + ythrow TDeserializationException() << what << " must be greater or equal to zero"; } if (result > Max<ui8>()) { diff --git a/library/cpp/type_info/ut/type_deserialize.cpp b/library/cpp/type_info/ut/type_deserialize.cpp index 9e93a26bee3..7d5f60bad04 100644 --- a/library/cpp/type_info/ut/type_deserialize.cpp +++ b/library/cpp/type_info/ut/type_deserialize.cpp @@ -120,6 +120,40 @@ TEST(TypeDeserialize, Decimal) { ASSERT_DESERIALIZED_EQ(NTi::Decimal(20, 10), R"({type_name=decimal; precision=20; scale=10})"); ASSERT_DESERIALIZED_EQ(NTi::Decimal(20, 10), R"({scale=10; type_name=decimal; precision=20})"); ASSERT_DESERIALIZED_EQ(NTi::Decimal(10, 10), R"({type_name=decimal; precision=10; scale=10})"); + ASSERT_DESERIALIZED_EQ(NTi::Decimal(10, 0), R"({type_name=decimal; precision=10; scale=0})"); +} + +TEST(TypeDeserialize, DecimalBadTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; precision=0; scale=10})"); + }(), + NTi::TDeserializationException, R"(invalid zero "precision")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; precision=-2; scale=10})"); + }(), + NTi::TDeserializationException, R"("precision" must be greater or equal to zero)"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; precision=2; scale=-2})"); + }(), + NTi::TDeserializationException, R"("scale" must be greater or equal to zero)"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; precision=2; scale=-2})"); + }(), + NTi::TDeserializationException, R"("scale" must be greater or equal to zero)"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; precision=1000; scale=2})"); + }(), + NTi::TDeserializationException, R"("precision" is too big)"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; precision=2; scale=1000})"); + }(), + NTi::TDeserializationException, R"("scale" is too big)"); } TEST(TypeDeserialize, DecimalMissingTypeParameters) { |