2323#include < memory>
2424#include < string>
2525#include < string_view>
26+ #include < unordered_map>
27+ #include < utility>
2628#include < vector>
2729
2830#include < arrow/array.h>
3840
3941#include " iceberg/arrow/arrow_io_internal.h"
4042#include " iceberg/avro/avro_register.h"
43+ #include " iceberg/data/data_writer.h"
4144#include " iceberg/expression/literal.h"
4245#include " iceberg/file_format.h"
4346#include " iceberg/file_reader.h"
4447#include " iceberg/file_writer.h"
4548#include " iceberg/parquet/parquet_register.h"
49+ #include " iceberg/partition_spec.h"
50+ #include " iceberg/row/partition_values.h"
4651#include " iceberg/schema.h"
4752#include " iceberg/schema_field.h"
4853#include " iceberg/schema_internal.h"
@@ -72,6 +77,12 @@ struct DefaultValueEndToEndParam {
7277 bool avro_skip_datum = true ;
7378};
7479
80+ struct WriteDefaultEndToEndParam {
81+ std::string name;
82+ FileFormatType format;
83+ std::string path;
84+ };
85+
7586class DefaultValueEndToEndTest
7687 : public UpdateTestBase,
7788 public ::testing::WithParamInterface<DefaultValueEndToEndParam> {
@@ -86,6 +97,37 @@ class DefaultValueEndToEndTest
8697 }
8798};
8899
100+ class WriteDefaultEndToEndTest
101+ : public ::testing::TestWithParam<WriteDefaultEndToEndParam> {
102+ protected:
103+ static void SetUpTestSuite () {
104+ parquet::RegisterAll ();
105+ avro::RegisterAll ();
106+ }
107+
108+ void SetUp () override { file_io_ = arrow::ArrowFileSystemFileIO::MakeMockFileIO (); }
109+
110+ std::shared_ptr<::arrow::Array> CreateArray (const Schema& schema,
111+ std::string_view json) {
112+ ArrowSchema arrow_c_schema;
113+ ICEBERG_THROW_NOT_OK (ToArrowSchema (schema, &arrow_c_schema));
114+ auto arrow_schema = ::arrow::ImportType (&arrow_c_schema).ValueOrDie ();
115+ return ::arrow::json::ArrayFromJSONString (::arrow::struct_ (arrow_schema->fields ()),
116+ std::string (json))
117+ .ValueOrDie ();
118+ }
119+
120+ static std::unordered_map<std::string, std::string> FormatProperties (
121+ FileFormatType format) {
122+ if (format == FileFormatType::kParquet ) {
123+ return {{" write.parquet.compression-codec" , " uncompressed" }};
124+ }
125+ return {};
126+ }
127+
128+ std::shared_ptr<FileIO> file_io_;
129+ };
130+
89131TEST_P (DefaultValueEndToEndTest, WriteEvolveReadFillsInitialDefault) {
90132 const auto & param = GetParam ();
91133 ICEBERG_UNWRAP_OR_FAIL (auto original_schema, table_->schema ());
@@ -186,6 +228,50 @@ TEST_P(DefaultValueEndToEndTest, WriteEvolveReadFillsInitialDefault) {
186228 ASSERT_FALSE (next_batch.has_value ());
187229}
188230
231+ TEST_P (WriteDefaultEndToEndTest, MissingColumnUsesWriteDefault) {
232+ const auto & param = GetParam ();
233+ auto input_schema = std::make_shared<Schema>(
234+ std::vector<SchemaField>{SchemaField::MakeRequired (1 , " id" , int32 ())});
235+ auto write_schema = std::make_shared<Schema>(std::vector<SchemaField>{
236+ SchemaField::MakeRequired (1 , " id" , int32 ()),
237+ SchemaField (2 , " added" , int32 (), /* optional=*/ false , /* doc=*/ {},
238+ std::make_shared<const Literal>(Literal::Int (42 )),
239+ std::make_shared<const Literal>(Literal::Int (7 ))),
240+ });
241+ DataWriterOptions options{
242+ .path = param.path ,
243+ .schema = write_schema,
244+ .input_schema = input_schema,
245+ .spec = PartitionSpec::Unpartitioned (),
246+ .partition = PartitionValues{},
247+ .format = param.format ,
248+ .io = file_io_,
249+ .properties = FormatProperties (param.format ),
250+ };
251+
252+ ICEBERG_UNWRAP_OR_FAIL (auto writer, DataWriter::Make (options));
253+ auto input = CreateArray (*input_schema, R"( [[1], [2]])" );
254+ ArrowArray arrow_array;
255+ ASSERT_TRUE (::arrow::ExportArray (*input, &arrow_array).ok ());
256+ ASSERT_THAT (writer->Write (&arrow_array), IsOk ());
257+ ASSERT_THAT (writer->Close (), IsOk ());
258+
259+ ICEBERG_UNWRAP_OR_FAIL (
260+ auto reader, ReaderFactoryRegistry::Open (
261+ param.format ,
262+ {.path = param.path , .io = file_io_, .projection = write_schema}));
263+ ICEBERG_UNWRAP_OR_FAIL (auto batch, reader->Next ());
264+ ASSERT_TRUE (batch.has_value ());
265+
266+ auto expected = CreateArray (*write_schema, R"( [[1, 7], [2, 7]])" );
267+ auto actual = ::arrow::ImportArray (&batch.value (), expected->type ()).ValueOrDie ();
268+
269+ // This file is written after the column exists, so the missing input column uses
270+ // write-default (7), not initial-default (42).
271+ ASSERT_TRUE (actual->Equals (expected))
272+ << " actual: " << actual->ToString () << " \n expected: " << expected->ToString ();
273+ }
274+
189275namespace {
190276
191277// Two-row column of `json` values at `type`, for the simple cases.
@@ -261,6 +347,16 @@ INSTANTIATE_TEST_SUITE_P(
261347 return info.param .name ;
262348 });
263349
350+ INSTANTIATE_TEST_SUITE_P (
351+ Formats, WriteDefaultEndToEndTest,
352+ ::testing::Values (WriteDefaultEndToEndParam{" parquet" , FileFormatType::kParquet ,
353+ " write-default.parquet" },
354+ WriteDefaultEndToEndParam{" avro" , FileFormatType::kAvro ,
355+ " write-default.avro" }),
356+ [](const ::testing::TestParamInfo<WriteDefaultEndToEndParam>& info) {
357+ return info.param .name ;
358+ });
359+
264360} // namespace
265361
266362} // namespace iceberg
0 commit comments