Skip to content

Commit 07af265

Browse files
committed
reject non-finite floating-point default values
1 parent 0825f56 commit 07af265

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

src/iceberg/schema_field.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "iceberg/schema_field.h"
2121

22+
#include <cmath>
2223
#include <format>
2324
#include <string_view>
2425
#include <utility>
@@ -168,6 +169,21 @@ Status ValidateDefault(const SchemaField& field, const Literal& value,
168169
return InvalidSchema("{} of field {} has type {} but expected {}", kind, field.name(),
169170
*value.type(), *field.type());
170171
}
172+
// A non-finite float/double default cannot be represented in JSON: the serializer emits
173+
// it as `null`, which reads back as an absent default. Reject it so the default is not
174+
// silently lost when the metadata round-trips. The literal type already matches the
175+
// field type here, so this only inspects the value for actual floating fields and other
176+
// types pay nothing.
177+
if (field.type()->type_id() == TypeId::kFloat &&
178+
!std::isfinite(std::get<float>(value.value()))) {
179+
return InvalidSchema("Invalid {} value for {}: value must be finite", kind,
180+
field.name());
181+
}
182+
if (field.type()->type_id() == TypeId::kDouble &&
183+
!std::isfinite(std::get<double>(value.value()))) {
184+
return InvalidSchema("Invalid {} value for {}: value must be finite", kind,
185+
field.name());
186+
}
171187
return {};
172188
}
173189

src/iceberg/test/schema_field_test.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
#include "iceberg/schema_field.h"
2121

2222
#include <format>
23+
#include <limits>
2324
#include <memory>
2425

2526
#include <gtest/gtest.h>
2627

2728
#include "iceberg/expression/literal.h"
29+
#include "iceberg/test/matchers.h"
2830
#include "iceberg/type.h"
2931
#include "iceberg/util/formatter.h" // IWYU pragma: keep
3032

@@ -170,4 +172,34 @@ TEST(SchemaFieldTest, CastDefaultValue) {
170172
}
171173
}
172174

175+
TEST(SchemaFieldTest, ValidateRejectsNonFiniteFloatingDefault) {
176+
// NaN / infinity cannot be represented in JSON (the serializer emits `null`, which
177+
// reads back as an absent default), so a non-finite floating default must be rejected.
178+
SchemaField nan_field(/*field_id=*/1, /*name=*/"f", float32(),
179+
/*optional=*/true, /*doc=*/"",
180+
std::make_shared<const Literal>(
181+
Literal::Float(std::numeric_limits<float>::quiet_NaN())));
182+
EXPECT_THAT(nan_field.Validate(), IsError(ErrorKind::kInvalidSchema));
183+
EXPECT_THAT(nan_field.Validate(), HasErrorMessage("must be finite"));
184+
185+
SchemaField inf_field(/*field_id=*/2, /*name=*/"d", float64(),
186+
/*optional=*/true, /*doc=*/"",
187+
std::make_shared<const Literal>(
188+
Literal::Double(std::numeric_limits<double>::infinity())));
189+
EXPECT_THAT(inf_field.Validate(), IsError(ErrorKind::kInvalidSchema));
190+
191+
SchemaField neg_inf_field(/*field_id=*/3, /*name=*/"d2", float64(),
192+
/*optional=*/true, /*doc=*/"",
193+
std::make_shared<const Literal>(Literal::Double(
194+
-std::numeric_limits<double>::infinity())));
195+
EXPECT_THAT(neg_inf_field.Validate(), IsError(ErrorKind::kInvalidSchema));
196+
}
197+
198+
TEST(SchemaFieldTest, ValidateAcceptsFiniteFloatingDefault) {
199+
SchemaField field(/*field_id=*/1, /*name=*/"f", float32(),
200+
/*optional=*/true, /*doc=*/"",
201+
std::make_shared<const Literal>(Literal::Float(1.5f)));
202+
EXPECT_THAT(field.Validate(), IsOk());
203+
}
204+
173205
} // namespace iceberg

0 commit comments

Comments
 (0)