From 1ad24d3f711ef40642a7e867b0ee9d0057ab6f85 Mon Sep 17 00:00:00 2001 From: Paul Reeves Date: Fri, 3 Feb 2023 11:43:15 +0100 Subject: [PATCH] SaveBlobToFile (WIP) --- src/MyFirstUDRKit.cpp | 95 +++++++++++++++++++++++++-------------- test/testudrkit-tests.dml | 30 ++++++++++--- test/testudrkit.ddl | 31 ++++++++++++- 3 files changed, 116 insertions(+), 40 deletions(-) diff --git a/src/MyFirstUDRKit.cpp b/src/MyFirstUDRKit.cpp index 4cde255..3ea94e2 100644 --- a/src/MyFirstUDRKit.cpp +++ b/src/MyFirstUDRKit.cpp @@ -70,6 +70,14 @@ #include +#ifdef _WIN32 +#include +#define SYSERROR() GetLastError() +#else +#include +#define SYSERROR() errno +#endif + using namespace Firebird; @@ -243,8 +251,7 @@ FB_UDR_EXECUTE_FUNCTION throw Firebird::FbException(status, statusVector); } catch (...) { - // This generates an unrecognised C++ exception, which is insufficient. - throw std::runtime_error("Error writing stream to BLOB."); + throw std::runtime_error("Error writing file to BLOB."); } } @@ -267,7 +274,7 @@ engine udr; FB_UDR_BEGIN_FUNCTION(MFK_SaveBlobToFile) //BEGIN FB_UDR_MESSAGE(InMessage, - (FB_CHAR(8191), afilename) + (FB_VARCHAR(8191), afilename) (FB_BLOB, ablob) ); @@ -277,7 +284,8 @@ FB_UDR_MESSAGE(OutMessage, AutoRelease att; AutoRelease tra; -AutoRelease Blob; +AutoRelease blob; +ISC_STATUS_ARRAY statusVector = {0}; FB_UDR_EXECUTE_FUNCTION @@ -292,47 +300,68 @@ FB_UDR_EXECUTE_FUNCTION * DOC NOTE - We do not test for existence of the file here! */ - std::fstream File ( in->afilename.str , std::fstream::out | std::fstream::binary | std::fstream::app ); - if (! File.is_open()) { - out->result = -2; + std::ofstream FileWriter ( in->afilename.str , std::ofstream::binary ); + if (! FileWriter.is_open()) { + out->resultNull = FB_TRUE; + // Convert system error to a negative value and assign to result + out->result = ( SYSERROR() * -1 ); + // important to set this to false here otherwise the error code will + // be hidden and NULL returned to the firebird engine. This is because + // the test for out->resultNull has precedence. + if ( out->result < 0 ) { + out->resultNull = FB_FALSE; + } return; } - Blob.reset(att->openBlob(status, tra, &in->ablob, 0, nullptr)); - if (Blob == nullptr) { - out->result = -1; - return; - } + att.reset(context->getAttachment(status)); + tra.reset(context->getTransaction(status)); + blob.reset(att->openBlob(status, tra, &in->ablob, 0, nullptr)); + try { + if (blob == nullptr) { + out->resultNull = FB_TRUE; + out->result = -1; + return; + } - std::vector Buffer (MaxSegmentSize, 0); + std::vector Buffer (MaxSegmentSize, 0); + unsigned BytesRead = 0; - unsigned BytesRead = 0; - - for (bool Eof = false; !Eof; ) - { - switch (Blob->getSegment( status, MaxSegmentSize, Buffer.data(), &BytesRead)) + for (bool Eof = false; !Eof; ) { - case IStatus::RESULT_OK: - case IStatus::RESULT_SEGMENT: + switch ( blob->getSegment ( status, MaxSegmentSize, Buffer.data(), &BytesRead)) { - File.write( Buffer.data(), Buffer.size() ); - out->result += BytesRead; - continue; - } - default: - { - Blob->close( status ); // will close interface - Blob.release(); - Eof = true; - break; + case IStatus::RESULT_OK: + case IStatus::RESULT_SEGMENT: + { + FileWriter.write( (char *) Buffer.data(), Buffer.size() ); + out->result += BytesRead; + continue; + } + default: + { + blob->close( status ); // will close interface + blob->release(); + Eof = true; + break; + } } } + // If we have got this far then we have written the blob. + // out->resultNULL ***must*** be set to false or else the function will return NULL + out->resultNull = FB_FALSE; + Buffer.clear(); + FileWriter.close(); + } + catch ( const FbException& error ) + { + throw Firebird::FbException(status, statusVector); + } + catch (...) { + throw std::runtime_error("Error writing BLOB to file."); } - - Buffer.clear(); - File.close(); } diff --git a/test/testudrkit-tests.dml b/test/testudrkit-tests.dml index a063999..505939d 100644 --- a/test/testudrkit-tests.dml +++ b/test/testudrkit-tests.dml @@ -20,15 +20,33 @@ execute procedure load_blob(3); execute procedure load_blob(4); commit; -select cast ( description as varchar(32) ) as DESCRIPTION +select cast ( description as varchar(16) ) as DESCRIPTION , file_type , cast (left(source_file,32) as varchar(32)) as source_file , source_status --- , source_bytes D_BIGINT --- , target_file D_PATH --- , target_status D_STATUS --- , target_bytes D_BIGINT --- , the_blob D_BLOB + , cast (left(target_file,32) as varchar(32)) as target_file + , target_status + , target_bytes from test_blobs; commit; +set term ^; +shell + mkdir --verbose /tmp/testudrkit + ls /tmp/testudrkit +^ +set term ;^ + +execute procedure save_blob(1, '/tmp/testudrkit/fb.conf' ); +execute procedure save_blob(2, '/tmp/testudrkit/fb.log' ); +commit; + +select cast ( description as varchar(16) ) as DESCRIPTION + , file_type + , cast (left(source_file,32) as varchar(32)) as source_file + , source_status + , cast (left(target_file,32) as varchar(32)) as target_file + , target_status + , cast (target_bytes as varchar(16) ) as target_bytes +from test_blobs; +commit; diff --git a/test/testudrkit.ddl b/test/testudrkit.ddl index dd338b3..aec087c 100644 --- a/test/testudrkit.ddl +++ b/test/testudrkit.ddl @@ -85,10 +85,39 @@ begin EXCEPTION E_BLOB_EXCEPTION 'Error updating test_blobs'; end +end ^ + +set term ;^ + + +set term ^; +create or alter procedure save_blob ( a_test_blob_id D_ID, atarget_file D_PATH ) sql security definer +as + declare theblob D_BLOB; + declare result D_BIGINT; + declare status D_STATUS; +begin + + select the_blob from test_blobs where test_blobs_id = :a_test_blob_id into :theblob; + select SaveBlobToFile( :atarget_file, :theblob ) from rdb$database into :result; + + if ( result >= 0 ) then + status = 'W'; + else + status = 'F'; + + update test_blobs + set target_file = :atarget_file + , target_bytes = :result + , target_status = :status + where test_blobs_id = :a_test_blob_id; + when any do begin + EXCEPTION E_BLOB_EXCEPTION 'Error updating test_blobs'; + end + end ^ set term ;^ -