Skip to content

Commit 93a5aa5

Browse files
committed
reject non-finite floating-point default values
1 parent cd4ca42 commit 93a5aa5

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

src/iceberg/schema_field.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
#include "iceberg/schema_field.h"
2121

22+
#include <cmath>
2223
#include <format>
2324
#include <string_view>
2425
#include <utility>
26+
#include <variant>
2527

2628
#include "iceberg/expression/literal.h"
2729
#include "iceberg/type.h"
@@ -124,6 +126,18 @@ Status ValidateDefault(const SchemaField& field, const Literal& value,
124126
return InvalidSchema("{} of field {} has type {} but expected {}", kind, field.name(),
125127
*value.type(), *field.type());
126128
}
129+
// A non-finite float/double default cannot be represented in JSON: the serializer emits
130+
// it as `null`, which reads back as an absent default. Reject it so the default is not
131+
// silently lost when the metadata round-trips.
132+
const auto& literal_value = value.value();
133+
const bool non_finite = (std::holds_alternative<float>(literal_value) &&
134+
!std::isfinite(std::get<float>(literal_value))) ||
135+
(std::holds_alternative<double>(literal_value) &&
136+
!std::isfinite(std::get<double>(literal_value)));
137+
if (non_finite) {
138+
return InvalidSchema("Invalid {} value for {}: value must be finite", kind,
139+
field.name());
140+
}
127141
return {};
128142
}
129143

src/iceberg/test/schema_field_test.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +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

28+
#include "iceberg/expression/literal.h"
29+
#include "iceberg/test/matchers.h"
2730
#include "iceberg/type.h"
2831
#include "iceberg/util/formatter.h" // IWYU pragma: keep
2932

@@ -107,4 +110,34 @@ TEST(SchemaFieldTest, WithDoc) {
107110
}
108111
}
109112

113+
TEST(SchemaFieldTest, ValidateRejectsNonFiniteFloatingDefault) {
114+
// NaN / infinity cannot be represented in JSON (the serializer emits `null`, which
115+
// reads back as an absent default), so a non-finite floating default must be rejected.
116+
SchemaField nan_field(/*field_id=*/1, /*name=*/"f", float32(),
117+
/*optional=*/true, /*doc=*/"",
118+
std::make_shared<const Literal>(
119+
Literal::Float(std::numeric_limits<float>::quiet_NaN())));
120+
EXPECT_THAT(nan_field.Validate(), IsError(ErrorKind::kInvalidSchema));
121+
EXPECT_THAT(nan_field.Validate(), HasErrorMessage("must be finite"));
122+
123+
SchemaField inf_field(/*field_id=*/2, /*name=*/"d", float64(),
124+
/*optional=*/true, /*doc=*/"",
125+
std::make_shared<const Literal>(
126+
Literal::Double(std::numeric_limits<double>::infinity())));
127+
EXPECT_THAT(inf_field.Validate(), IsError(ErrorKind::kInvalidSchema));
128+
129+
SchemaField neg_inf_field(/*field_id=*/3, /*name=*/"d2", float64(),
130+
/*optional=*/true, /*doc=*/"",
131+
std::make_shared<const Literal>(Literal::Double(
132+
-std::numeric_limits<double>::infinity())));
133+
EXPECT_THAT(neg_inf_field.Validate(), IsError(ErrorKind::kInvalidSchema));
134+
}
135+
136+
TEST(SchemaFieldTest, ValidateAcceptsFiniteFloatingDefault) {
137+
SchemaField field(/*field_id=*/1, /*name=*/"f", float32(),
138+
/*optional=*/true, /*doc=*/"",
139+
std::make_shared<const Literal>(Literal::Float(1.5f)));
140+
EXPECT_THAT(field.Validate(), IsOk());
141+
}
142+
110143
} // namespace iceberg

0 commit comments

Comments
 (0)