Skip to content

Commit 54f9297

Browse files
authored
25-3-1: Make it possible to backup out-of-range decimal values (#26537)
2 parents ac72665 + 7b2f5c0 commit 54f9297

File tree

2 files changed

+130
-3
lines changed

2 files changed

+130
-3
lines changed

ydb/core/tx/datashard/type_serialization.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ TString DecimalToString(const std::pair<ui64, i64>& loHi, const NScheme::TTypeIn
1515
using namespace NYql::NDecimal;
1616

1717
TInt128 val = FromHalfs(loHi.first, loHi.second);
18-
return ToString(val, typeInfo.GetDecimalType().GetPrecision(), typeInfo.GetDecimalType().GetScale());
18+
const char* result = ToString(val, MaxPrecision /*typeInfo.GetDecimalType().GetPrecision()*/, typeInfo.GetDecimalType().GetScale());
19+
Y_ENSURE(result);
20+
21+
return result;
1922
}
2023

2124
TString DyNumberToString(TStringBuf data) {
@@ -36,11 +39,15 @@ TString PgToString(TStringBuf data, const NScheme::TTypeInfo& typeInfo) {
3639
}
3740

3841
bool DecimalToStream(const std::pair<ui64, i64>& loHi, IOutputStream& out, TString& err, const NScheme::TTypeInfo& typeInfo) {
39-
Y_UNUSED(err);
4042
using namespace NYql::NDecimal;
4143

4244
TInt128 val = FromHalfs(loHi.first, loHi.second);
43-
out << ToString(val, typeInfo.GetDecimalType().GetPrecision(), typeInfo.GetDecimalType().GetScale());
45+
const char* result = ToString(val, MaxPrecision /*typeInfo.GetDecimalType().GetPrecision()*/, typeInfo.GetDecimalType().GetScale());
46+
if (!result) [[unlikely]] {
47+
err = "Invalid Decimal binary representation";
48+
return false;
49+
}
50+
out << result;
4451
return true;
4552
}
4653

ydb/core/tx/schemeshard/ut_export/ut_export.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2969,4 +2969,124 @@ attributes {
29692969
NLs::IndexState(NKikimrSchemeOp::EIndexStateReady),
29702970
NLs::IndexKeys({"value"})});
29712971
}
2972+
2973+
Y_UNIT_TEST(DecimalOutOfRange) {
2974+
EnvOptions().DisableStatsBatching(true);
2975+
Env(); // Init test env
2976+
ui64 txId = 100;
2977+
2978+
TestCreateTable(Runtime(), ++txId, "/MyRoot", R"(
2979+
Name: "Table1"
2980+
Columns { Name: "key" Type: "Uint64" }
2981+
Columns { Name: "value" Type: "Decimal" }
2982+
KeyColumnNames: ["key"]
2983+
)");
2984+
Env().TestWaitNotification(Runtime(), txId);
2985+
2986+
// Write a normal decimal value
2987+
// 10.0^13-1 (scale 9) = 0x21e19e0c9ba76a53600
2988+
{
2989+
ui64 key = 1u;
2990+
std::pair<ui64, i64> value = { 0x19e0c9ba76a53600ULL, 0x21eULL };
2991+
UploadRow(Runtime(), "/MyRoot/Table1", 0, {1}, {2}, {TCell::Make(key)}, {TCell::Make(value)});
2992+
}
2993+
// Write a decimal value that is out of range for precision 22
2994+
// 10.0^13 (scale 9) = 10^22 = 0x21e19e0c9bab2400000
2995+
{
2996+
ui64 key = 2u;
2997+
std::pair<ui64, i64> value = { 0x19e0c9bab2400000ULL, 0x21eULL };
2998+
UploadRow(Runtime(), "/MyRoot/Table1", 0, {1}, {2}, {TCell::Make(key)}, {TCell::Make(value)});
2999+
}
3000+
3001+
TestExport(Runtime(), ++txId, "/MyRoot", Sprintf(R"(
3002+
ExportToS3Settings {
3003+
endpoint: "localhost:%d"
3004+
scheme: HTTP
3005+
items {
3006+
source_path: "/MyRoot/Table1"
3007+
destination_prefix: "Backup1"
3008+
}
3009+
}
3010+
)", S3Port()));
3011+
Env().TestWaitNotification(Runtime(), txId);
3012+
3013+
TestGetExport(Runtime(), txId, "/MyRoot", Ydb::StatusIds::SUCCESS);
3014+
3015+
UNIT_ASSERT(HasS3File("/Backup1/metadata.json"));
3016+
UNIT_ASSERT(HasS3File("/Backup1/data_00.csv"));
3017+
UNIT_ASSERT_STRINGS_EQUAL(GetS3FileContent("/Backup1/data_00.csv"),
3018+
"1,9999999999999\n"
3019+
"2,10000000000000\n");
3020+
3021+
TestImport(Runtime(), ++txId, "/MyRoot", Sprintf(R"(
3022+
ImportFromS3Settings {
3023+
endpoint: "localhost:%d"
3024+
scheme: HTTP
3025+
items {
3026+
source_prefix: "Backup1"
3027+
destination_path: "/MyRoot/Table2"
3028+
}
3029+
}
3030+
)", S3Port()));
3031+
Env().TestWaitNotification(Runtime(), txId);
3032+
3033+
TestGetImport(Runtime(), txId, "/MyRoot", Ydb::StatusIds::SUCCESS);
3034+
3035+
TestExport(Runtime(), ++txId, "/MyRoot", Sprintf(R"(
3036+
ExportToS3Settings {
3037+
endpoint: "localhost:%d"
3038+
scheme: HTTP
3039+
items {
3040+
source_path: "/MyRoot/Table2"
3041+
destination_prefix: "Backup2"
3042+
}
3043+
}
3044+
)", S3Port()));
3045+
Env().TestWaitNotification(Runtime(), txId);
3046+
3047+
TestGetExport(Runtime(), txId, "/MyRoot", Ydb::StatusIds::SUCCESS);
3048+
3049+
// Note: out-of-range values are restored as inf
3050+
UNIT_ASSERT(HasS3File("/Backup2/metadata.json"));
3051+
UNIT_ASSERT(HasS3File("/Backup2/data_00.csv"));
3052+
UNIT_ASSERT_STRINGS_EQUAL(GetS3FileContent("/Backup2/data_00.csv"),
3053+
"1,9999999999999\n"
3054+
"2,inf\n");
3055+
}
3056+
3057+
Y_UNIT_TEST(CorruptedDecimalValue) {
3058+
EnvOptions().DisableStatsBatching(true);
3059+
Env(); // Init test env
3060+
ui64 txId = 100;
3061+
3062+
TestCreateTable(Runtime(), ++txId, "/MyRoot", R"(
3063+
Name: "Table1"
3064+
Columns { Name: "key" Type: "Uint64" }
3065+
Columns { Name: "value" Type: "Decimal" }
3066+
KeyColumnNames: ["key"]
3067+
)");
3068+
Env().TestWaitNotification(Runtime(), txId);
3069+
3070+
// Write a decimal value that is way out of range for max precision 35
3071+
// 10^38 = 0x4b3b4ca85a86c47a098a224000000000
3072+
{
3073+
ui64 key = 1u;
3074+
std::pair<ui64, i64> value = { 0x098a224000000000ULL, 0x4b3b4ca85a86c47aULL };
3075+
UploadRow(Runtime(), "/MyRoot/Table1", 0, {1}, {2}, {TCell::Make(key)}, {TCell::Make(value)});
3076+
}
3077+
3078+
TestExport(Runtime(), ++txId, "/MyRoot", Sprintf(R"(
3079+
ExportToS3Settings {
3080+
endpoint: "localhost:%d"
3081+
scheme: HTTP
3082+
items {
3083+
source_path: "/MyRoot/Table1"
3084+
destination_prefix: "Backup1"
3085+
}
3086+
}
3087+
)", S3Port()));
3088+
Env().TestWaitNotification(Runtime(), txId);
3089+
3090+
TestGetExport(Runtime(), txId, "/MyRoot", Ydb::StatusIds::CANCELLED);
3091+
}
29723092
}

0 commit comments

Comments
 (0)