diff --git a/ydb/core/io_formats/cell_maker/cell_maker.cpp b/ydb/core/io_formats/cell_maker/cell_maker.cpp index e1b07e187ade..618285bf5971 100644 --- a/ydb/core/io_formats/cell_maker/cell_maker.cpp +++ b/ydb/core/io_formats/cell_maker/cell_maker.cpp @@ -518,13 +518,13 @@ bool CheckCellValue(const TCell& cell, const NScheme::TTypeInfo& typeInfo) { case NScheme::NTypeIds::Interval: return (ui64)std::abs(cell.AsValue()) < NUdf::MAX_TIMESTAMP; case NScheme::NTypeIds::Date32: - return cell.AsValue() < NUdf::MAX_DATE32; + return cell.AsValue() >= NUdf::MIN_DATE32 && cell.AsValue() <= NUdf::MAX_DATE32; case NScheme::NTypeIds::Datetime64: - return cell.AsValue() < NUdf::MAX_DATETIME64; + return cell.AsValue() >= NUdf::MIN_DATETIME64 && cell.AsValue() <= NUdf::MAX_DATETIME64; case NScheme::NTypeIds::Timestamp64: - return cell.AsValue() < NUdf::MAX_TIMESTAMP64; + return cell.AsValue() >= NUdf::MIN_TIMESTAMP64 && cell.AsValue() <= NUdf::MAX_TIMESTAMP64; case NScheme::NTypeIds::Interval64: - return std::abs(cell.AsValue()) < NUdf::MAX_INTERVAL64; + return std::abs(cell.AsValue()) <= NUdf::MAX_INTERVAL64; case NScheme::NTypeIds::Utf8: return NYql::IsUtf8(cell.AsBuf()); case NScheme::NTypeIds::Yson: diff --git a/ydb/core/kqp/ut/query/kqp_query_ut.cpp b/ydb/core/kqp/ut/query/kqp_query_ut.cpp index d4d548f242eb..7ad887e16fe6 100644 --- a/ydb/core/kqp/ut/query/kqp_query_ut.cpp +++ b/ydb/core/kqp/ut/query/kqp_query_ut.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -74,6 +75,115 @@ Y_UNIT_TEST_SUITE(KqpQuery) { UNIT_ASSERT_VALUES_EQUAL(counters.RecompileRequestGet()->Val(), 1); } + Y_UNIT_TEST_TWIN(ExtendedTimeOutOfBounds, BulkUpsert) { + auto settings = TKikimrSettings().SetWithSampleTables(false); + TKikimrRunner kikimr(settings); + + auto queryClient = kikimr.GetQueryClient(); + auto tableClient = kikimr.GetTableClient(); + + { + const std::string query = R"( + CREATE TABLE `/Root/TimeTable` ( + Key UInt32 NOT NULL, + V_Date32 Date32, + V_Datetime64 Datetime64, + V_Timestamp64 Timestamp64, + V_Interval64 Interval64, + PRIMARY KEY (Key) + ); + )"; + auto result = queryClient.ExecuteQuery(query, NQuery::TTxControl::NoTx()).ExtractValueSync(); + UNIT_ASSERT_C(result.IsSuccess(), result.GetIssues().ToString()); + } + + auto fUpsertAndCheck = [&](ui32 key, T value, bool success) { + std::string colName; + if (BulkUpsert) { + TValueBuilder rows; + rows.BeginList(); + rows.AddListItem().BeginStruct().AddMember("Key").Uint32(key); + if constexpr (std::is_same_v) { + rows.AddMember("V_Date32").Date32(std::chrono::sys_time(TWideDays(value))); + colName = "V_Date32"; + } else if constexpr (std::is_same_v) { + rows.AddMember("V_Datetime64").Datetime64(std::chrono::sys_time(TWideSeconds(value))); + colName = "V_Datetime64"; + } else if constexpr (std::is_same_v) { + rows.AddMember("V_Timestamp64").Timestamp64(std::chrono::sys_time(TWideMicroseconds(value))); + colName = "V_Timestamp64"; + } else if constexpr (std::is_same_v) { + rows.AddMember("V_Interval64").Interval64(TWideMicroseconds(value)); + colName = "V_Interval64"; + } else { + UNIT_ASSERT_C(false, "Unsupported type"); + } + rows.EndStruct().EndList(); + + auto result = tableClient.BulkUpsert("/Root/TimeTable", rows.Build()).GetValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.IsSuccess(), success, result.GetIssues().ToString()); + } else { + auto params = std::move(TParamsBuilder().AddParam("$key").Uint32(key).Build()); + if constexpr (std::is_same_v) { + params.AddParam("$param").Date32(std::chrono::sys_time(TWideDays(value))).Build(); + colName = "V_Date32"; + } else if constexpr (std::is_same_v) { + params.AddParam("$param").Datetime64(std::chrono::sys_time(TWideSeconds(value))).Build(); + colName = "V_Datetime64"; + } else if constexpr (std::is_same_v) { + params.AddParam("$param").Timestamp64(std::chrono::sys_time(TWideMicroseconds(value))).Build(); + colName = "V_Timestamp64"; + } else if constexpr (std::is_same_v) { + params.AddParam("$param").Interval64(TWideMicroseconds(value)).Build(); + colName = "V_Interval64"; + } else { + UNIT_ASSERT_C(false, "Unsupported type"); + } + + auto result = queryClient.ExecuteQuery(Sprintf(R"( + UPSERT INTO `/Root/TimeTable` (Key, %s) VALUES ($key, $param); + )", colName.c_str()), NQuery::TTxControl::NoTx(), params.Build()).ExtractValueSync(); + UNIT_ASSERT_VALUES_EQUAL_C(result.IsSuccess(), success, result.GetIssues().ToString()); + } + }; + + { + // Date32 + fUpsertAndCheck(1, TWideDays(0), /* success */ true); // Basic + fUpsertAndCheck(2, TWideDays(NYql::NUdf::MIN_DATE32), /* success */ true); // Min is inclusive + fUpsertAndCheck(3, TWideDays(NYql::NUdf::MAX_DATE32), /* success */ true); // Max is inclusive + fUpsertAndCheck(4, TWideDays(NYql::NUdf::MIN_DATE32 - 1), /* success */ false); // Out of bounds + fUpsertAndCheck(5, TWideDays(NYql::NUdf::MAX_DATE32 + 1), /* success */ false); // Out of bounds + } + + { + // Datetime64 + fUpsertAndCheck(11, TWideSeconds(0), /* success */ true); // Basic + fUpsertAndCheck(12, TWideSeconds(NYql::NUdf::MIN_DATETIME64), /* success */ true); // Min is inclusive + fUpsertAndCheck(13, TWideSeconds(NYql::NUdf::MAX_DATETIME64), /* success */ true); // Max is inclusive + fUpsertAndCheck(14, TWideSeconds(NYql::NUdf::MIN_DATETIME64 - 1), /* success */ false); // Out of bounds + fUpsertAndCheck(15, TWideSeconds(NYql::NUdf::MAX_DATETIME64 + 1), /* success */ false); // Out of bounds + } + + { + // Timestamp64 + fUpsertAndCheck(21, TWideMicroseconds(0), /* success */ true); // Basic + fUpsertAndCheck(22, TWideMicroseconds(NYql::NUdf::MIN_TIMESTAMP64), /* success */ true); // Min is inclusive + fUpsertAndCheck(23, TWideMicroseconds(NYql::NUdf::MAX_TIMESTAMP64), /* success */ true); // Max is inclusive + fUpsertAndCheck(24, TWideMicroseconds(NYql::NUdf::MIN_TIMESTAMP64 - 1), /* success */ false); // Out of bounds + fUpsertAndCheck(25, TWideMicroseconds(NYql::NUdf::MAX_TIMESTAMP64 + 1), /* success */ false); // Out of bounds + } + + { + // Interval64 + fUpsertAndCheck(31, static_cast(0), /* success */ true); // Basic + fUpsertAndCheck(32, NYql::NUdf::MAX_INTERVAL64, /* success */ true); // Max is inclusive + fUpsertAndCheck(33, -NYql::NUdf::MAX_INTERVAL64, /* success */ true); // -Max is inclusive + fUpsertAndCheck(34, NYql::NUdf::MAX_INTERVAL64 + 1, /* success */ false); // Out of bounds + fUpsertAndCheck(35, -(NYql::NUdf::MAX_INTERVAL64 + 1), /* success */ false); // Out of bounds + } + } + Y_UNIT_TEST_TWIN(DecimalOutOfPrecisionBulk, EnableParameterizedDecimal) { TKikimrSettings serverSettings; serverSettings.FeatureFlags.SetEnableParameterizedDecimal(EnableParameterizedDecimal); diff --git a/ydb/core/ydb_convert/ydb_convert.cpp b/ydb/core/ydb_convert/ydb_convert.cpp index bbee3bed6600..90f2c0ea1468 100644 --- a/ydb/core/ydb_convert/ydb_convert.cpp +++ b/ydb/core/ydb_convert/ydb_convert.cpp @@ -480,28 +480,28 @@ Y_FORCE_INLINE void ConvertData(NUdf::TDataTypeId typeId, const Ydb::Value& valu break; case NUdf::TDataType::Id: CheckTypeId(value.value_case(), Ydb::Value::kInt32Value, "Date32"); - if (value.int32_value() >= NUdf::MAX_DATE32) { + if (value.int32_value() < NUdf::MIN_DATE32 || value.int32_value() > NUdf::MAX_DATE32) { throw yexception() << "Invalid Date32 value"; } res.SetInt32(value.int32_value()); break; case NUdf::TDataType::Id: CheckTypeId(value.value_case(), Ydb::Value::kInt64Value, "Datetime64"); - if (value.int64_value() >= NUdf::MAX_DATETIME64) { + if (value.int64_value() < NUdf::MIN_DATETIME64 || value.int64_value() > NUdf::MAX_DATETIME64) { throw yexception() << "Invalid Datetime64 value"; } res.SetInt64(value.int64_value()); break; case NUdf::TDataType::Id: CheckTypeId(value.value_case(), Ydb::Value::kInt64Value, "Timestamp64"); - if (value.int64_value() >= NUdf::MAX_TIMESTAMP64) { + if (value.int64_value() < NUdf::MIN_TIMESTAMP64 || value.int64_value() > NUdf::MAX_TIMESTAMP64) { throw yexception() << "Invalid Timestamp64 value"; } res.SetInt64(value.int64_value()); break; case NUdf::TDataType::Id: CheckTypeId(value.value_case(), Ydb::Value::kInt64Value, "Interval64"); - if (std::abs(value.int64_value()) >= NUdf::MAX_INTERVAL64) { + if (std::abs(value.int64_value()) > NUdf::MAX_INTERVAL64) { throw yexception() << "Invalid Interval64 value"; } res.SetInt64(value.int64_value()); @@ -1137,19 +1137,19 @@ bool CheckValueData(NScheme::TTypeInfo type, const TCell& cell, TString& err) { break; case NScheme::NTypeIds::Date32: - ok = cell.AsValue() < NUdf::MAX_DATE32; + ok = cell.AsValue() >= NUdf::MIN_DATE32 && cell.AsValue() <= NUdf::MAX_DATE32; break; case NScheme::NTypeIds::Datetime64: - ok = cell.AsValue() < NUdf::MAX_DATETIME64; + ok = cell.AsValue() >= NUdf::MIN_DATETIME64 && cell.AsValue() <= NUdf::MAX_DATETIME64; break; case NScheme::NTypeIds::Timestamp64: - ok = cell.AsValue() < NUdf::MAX_TIMESTAMP64; + ok = cell.AsValue() >= NUdf::MIN_TIMESTAMP64 && cell.AsValue() <= NUdf::MAX_TIMESTAMP64; break; case NScheme::NTypeIds::Interval64: - ok = std::abs(cell.AsValue()) < NUdf::MAX_INTERVAL64; + ok = std::abs(cell.AsValue()) <= NUdf::MAX_INTERVAL64; break; case NScheme::NTypeIds::Utf8: