3131#include " paimon/common/utils/delta_varint_compressor.h"
3232#include " paimon/data/blob.h"
3333#include " paimon/io/byte_array_input_stream.h"
34+ #include " paimon/logging.h"
3435
3536namespace paimon ::blob {
3637
3738BlobFormatWriter::BlobFormatWriter (const std::shared_ptr<OutputStream>& out, const std::string& uri,
3839 const std::shared_ptr<arrow::DataType>& data_type,
40+ bool write_null_on_missing_file,
41+ bool write_null_on_fetch_failure,
3942 const std::shared_ptr<FileSystem>& fs,
4043 const std::shared_ptr<MemoryPool>& pool)
41- : out_(out), uri_(uri), data_type_(data_type), fs_(fs), pool_(pool) {
44+ : out_(out),
45+ uri_ (uri),
46+ data_type_(data_type),
47+ fs_(fs),
48+ pool_(pool),
49+ write_null_on_missing_file_(write_null_on_missing_file),
50+ write_null_on_fetch_failure_(write_null_on_fetch_failure) {
4251 metrics_ = std::make_shared<MetricsImpl>();
4352 tmp_buffer_ = Bytes::AllocateBytes (kTmpBufferSize , pool_.get ());
4453 magic_number_bytes_ = IntegerToLittleEndian<int32_t >(BlobDefs::kMagicNumber , pool_);
54+ logger_ = Logger::GetLogger (" BlobFormatWriter" );
4555}
4656
4757Result<std::unique_ptr<BlobFormatWriter>> BlobFormatWriter::Create (
4858 const std::shared_ptr<OutputStream>& out, const std::shared_ptr<arrow::DataType>& data_type,
59+ bool write_null_on_missing_file, bool write_null_on_fetch_failure,
4960 const std::shared_ptr<FileSystem>& fs, const std::shared_ptr<MemoryPool>& pool) {
5061 if (out == nullptr ) {
5162 return Status::Invalid (" blob format writer create failed. out is nullptr" );
@@ -65,7 +76,8 @@ Result<std::unique_ptr<BlobFormatWriter>> BlobFormatWriter::Create(
6576 fmt::format (" field {} is not BLOB" , data_type->field (0 )->ToString ()));
6677 }
6778 PAIMON_ASSIGN_OR_RAISE (std::string uri, out->GetUri ());
68- return std::unique_ptr<BlobFormatWriter>(new BlobFormatWriter (out, uri, data_type, fs, pool));
79+ return std::unique_ptr<BlobFormatWriter>(new BlobFormatWriter (
80+ out, uri, data_type, write_null_on_missing_file, write_null_on_fetch_failure, fs, pool));
6981}
7082
7183Status BlobFormatWriter::AddBatch (ArrowArray* batch) {
@@ -126,27 +138,41 @@ Status BlobFormatWriter::Finish() {
126138}
127139
128140Status BlobFormatWriter::WriteBlob (std::string_view blob_data) {
129- crc32_ = 0 ;
130- PAIMON_ASSIGN_OR_RAISE (int64_t previous_pos, out_->GetPos ());
131-
132- // write magic number
133- PAIMON_RETURN_NOT_OK (WriteWithCrc32 (magic_number_bytes_->data (), magic_number_bytes_->size ()));
134-
135- // write blob content
141+ // Open the blob input stream before writing any bytes, so that a failed fetch can be
142+ // converted to a NULL element without leaving partial data in the output stream.
136143 // Dynamically check whether blob_data is a serialized BlobDescriptor (by magic header)
137144 // rather than relying on blob_as_descriptor_ config. This is consistent with Java behavior:
138145 // at write time, the input bytes are auto-detected as descriptor or raw data.
139146 std::unique_ptr<InputStream> in;
140147 PAIMON_ASSIGN_OR_RAISE (bool is_descriptor,
141148 BlobDescriptor::IsBlobDescriptor (blob_data.data (), blob_data.size ()));
142149 if (is_descriptor) {
143- PAIMON_ASSIGN_OR_RAISE (std::unique_ptr<Blob> blob,
144- Blob::FromDescriptor (blob_data.data (), blob_data.size ()));
145- PAIMON_ASSIGN_OR_RAISE (in, blob->NewInputStream (fs_));
150+ Result<std::unique_ptr<InputStream>> opened = OpenDescriptorInputStream (blob_data);
151+ if (!opened.ok ()) {
152+ const Status& status = opened.status ();
153+ // A missing file is only handled by 'blob-write-null-on-missing-file'; other fetch
154+ // failures are only handled by 'blob-write-null-on-fetch-failure' (aligned with Java).
155+ bool write_null =
156+ status.IsNotExist () ? write_null_on_missing_file_ : write_null_on_fetch_failure_;
157+ if (write_null) {
158+ PAIMON_LOG_WARN (logger_, " Failed to open blob, writing NULL for BLOB field: %s" ,
159+ status.ToString ().c_str ());
160+ bin_lengths_.push_back (BlobDefs::kNullBinLength );
161+ return Status::OK ();
162+ }
163+ return status;
164+ }
165+ in = std::move (opened).value ();
146166 } else {
147167 in = std::make_unique<ByteArrayInputStream>(blob_data.data (), blob_data.size ());
148168 }
149169 PAIMON_ASSIGN_OR_RAISE (int64_t file_length, in->Length ());
170+
171+ crc32_ = 0 ;
172+ PAIMON_ASSIGN_OR_RAISE (int64_t previous_pos, out_->GetPos ());
173+
174+ // write magic number
175+ PAIMON_RETURN_NOT_OK (WriteWithCrc32 (magic_number_bytes_->data (), magic_number_bytes_->size ()));
150176 int64_t total_read_length = 0 ;
151177 int64_t read_len = std::min (file_length, static_cast <int64_t >(tmp_buffer_->size ()));
152178 while (read_len > 0 ) {
@@ -179,6 +205,13 @@ Status BlobFormatWriter::WriteBlob(std::string_view blob_data) {
179205 return Status::OK ();
180206}
181207
208+ Result<std::unique_ptr<InputStream>> BlobFormatWriter::OpenDescriptorInputStream (
209+ std::string_view blob_data) const {
210+ PAIMON_ASSIGN_OR_RAISE (std::unique_ptr<Blob> blob,
211+ Blob::FromDescriptor (blob_data.data (), blob_data.size ()));
212+ return blob->NewInputStream (fs_);
213+ }
214+
182215Status BlobFormatWriter::WriteBytes (const char * data, int64_t length) {
183216 PAIMON_ASSIGN_OR_RAISE (int64_t actual, out_->Write (data, length));
184217 if (actual != length) {
0 commit comments