diff --git a/src/iceberg/schema_field.cc b/src/iceberg/schema_field.cc index 6c8d10d97..5d55bb507 100644 --- a/src/iceberg/schema_field.cc +++ b/src/iceberg/schema_field.cc @@ -19,9 +19,11 @@ #include "iceberg/schema_field.h" +#include #include #include #include +#include #include "iceberg/expression/literal.h" #include "iceberg/type.h" @@ -124,6 +126,18 @@ Status ValidateDefault(const SchemaField& field, const Literal& value, return InvalidSchema("{} of field {} has type {} but expected {}", kind, field.name(), *value.type(), *field.type()); } + // A non-finite float/double default cannot be represented in JSON: the serializer emits + // it as `null`, which reads back as an absent default. Reject it so the default is not + // silently lost when the metadata round-trips. + const auto& literal_value = value.value(); + const bool non_finite = (std::holds_alternative(literal_value) && + !std::isfinite(std::get(literal_value))) || + (std::holds_alternative(literal_value) && + !std::isfinite(std::get(literal_value))); + if (non_finite) { + return InvalidSchema("Invalid {} value for {}: value must be finite", kind, + field.name()); + } return {}; } diff --git a/src/iceberg/test/schema_field_test.cc b/src/iceberg/test/schema_field_test.cc index c2cd6a758..48ac189cf 100644 --- a/src/iceberg/test/schema_field_test.cc +++ b/src/iceberg/test/schema_field_test.cc @@ -20,10 +20,13 @@ #include "iceberg/schema_field.h" #include +#include #include #include +#include "iceberg/expression/literal.h" +#include "iceberg/test/matchers.h" #include "iceberg/type.h" #include "iceberg/util/formatter.h" // IWYU pragma: keep @@ -107,4 +110,34 @@ TEST(SchemaFieldTest, WithDoc) { } } +TEST(SchemaFieldTest, ValidateRejectsNonFiniteFloatingDefault) { + // NaN / infinity cannot be represented in JSON (the serializer emits `null`, which + // reads back as an absent default), so a non-finite floating default must be rejected. + SchemaField nan_field(/*field_id=*/1, /*name=*/"f", float32(), + /*optional=*/true, /*doc=*/"", + std::make_shared( + Literal::Float(std::numeric_limits::quiet_NaN()))); + EXPECT_THAT(nan_field.Validate(), IsError(ErrorKind::kInvalidSchema)); + EXPECT_THAT(nan_field.Validate(), HasErrorMessage("must be finite")); + + SchemaField inf_field(/*field_id=*/2, /*name=*/"d", float64(), + /*optional=*/true, /*doc=*/"", + std::make_shared( + Literal::Double(std::numeric_limits::infinity()))); + EXPECT_THAT(inf_field.Validate(), IsError(ErrorKind::kInvalidSchema)); + + SchemaField neg_inf_field(/*field_id=*/3, /*name=*/"d2", float64(), + /*optional=*/true, /*doc=*/"", + std::make_shared(Literal::Double( + -std::numeric_limits::infinity()))); + EXPECT_THAT(neg_inf_field.Validate(), IsError(ErrorKind::kInvalidSchema)); +} + +TEST(SchemaFieldTest, ValidateAcceptsFiniteFloatingDefault) { + SchemaField field(/*field_id=*/1, /*name=*/"f", float32(), + /*optional=*/true, /*doc=*/"", + std::make_shared(Literal::Float(1.5f))); + EXPECT_THAT(field.Validate(), IsOk()); +} + } // namespace iceberg