Skip to content

Commit d4039a4

Browse files
committed
validate decimal default fits the field precision
1 parent cd4ca42 commit d4039a4

2 files changed

Lines changed: 35 additions & 0 deletions

File tree

src/iceberg/schema_field.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
#include <format>
2323
#include <string_view>
2424
#include <utility>
25+
#include <variant>
2526

2627
#include "iceberg/expression/literal.h"
2728
#include "iceberg/type.h"
29+
#include "iceberg/util/checked_cast.h"
30+
#include "iceberg/util/decimal.h"
2831
#include "iceberg/util/formatter.h" // IWYU pragma: keep
2932
#include "iceberg/util/macros.h"
3033

@@ -124,6 +127,18 @@ Status ValidateDefault(const SchemaField& field, const Literal& value,
124127
return InvalidSchema("{} of field {} has type {} but expected {}", kind, field.name(),
125128
*value.type(), *field.type());
126129
}
130+
// A decimal literal carries a precision in its type but stores only an unscaled value,
131+
// so a value that does not fit the field precision would pass the type check above yet
132+
// be rejected when the metadata is parsed back. Reject it here to keep the default
133+
// consistent with what the reader accepts.
134+
if (field.type()->type_id() == TypeId::kDecimal &&
135+
std::holds_alternative<Decimal>(value.value())) {
136+
const auto& decimal_type = internal::checked_cast<const DecimalType&>(*field.type());
137+
if (!std::get<Decimal>(value.value()).FitsInPrecision(decimal_type.precision())) {
138+
return InvalidSchema("{} of field {} does not fit precision {}", kind, field.name(),
139+
decimal_type.precision());
140+
}
141+
}
127142
return {};
128143
}
129144

src/iceberg/test/schema_field_test.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
#include <gtest/gtest.h>
2626

27+
#include "iceberg/expression/literal.h"
28+
#include "iceberg/test/matchers.h"
2729
#include "iceberg/type.h"
2830
#include "iceberg/util/formatter.h" // IWYU pragma: keep
2931

@@ -107,4 +109,22 @@ TEST(SchemaFieldTest, WithDoc) {
107109
}
108110
}
109111

112+
TEST(SchemaFieldTest, ValidateRejectsDecimalDefaultExceedingPrecision) {
113+
// A decimal default whose unscaled value needs more digits than the field precision has
114+
// the matching literal type (decimal(2, 1)) but does not fit; Validate must reject it
115+
// so it is never serialized to metadata the reader would later refuse to parse.
116+
SchemaField field(/*field_id=*/1, /*name=*/"d", decimal(2, 1),
117+
/*optional=*/true, /*doc=*/"",
118+
std::make_shared<const Literal>(Literal::Decimal(999, 2, 1)));
119+
EXPECT_THAT(field.Validate(), IsError(ErrorKind::kInvalidSchema));
120+
EXPECT_THAT(field.Validate(), HasErrorMessage("does not fit precision"));
121+
}
122+
123+
TEST(SchemaFieldTest, ValidateAcceptsDecimalDefaultWithinPrecision) {
124+
SchemaField field(/*field_id=*/1, /*name=*/"d", decimal(4, 1),
125+
/*optional=*/true, /*doc=*/"",
126+
std::make_shared<const Literal>(Literal::Decimal(999, 4, 1)));
127+
EXPECT_THAT(field.Validate(), IsOk());
128+
}
129+
110130
} // namespace iceberg

0 commit comments

Comments
 (0)