From 73c1ab807a327166a189eeda17458c01c6429c6d Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Fri, 16 Dec 2022 06:53:47 -0300 Subject: [PATCH] RDB$BLOB_UTIL system package. (#281) * RDB$BLOB_UTIL system package. * Do not checkout from engine when calling system packages. * Remove usage of Attachment::SyncGuard in RDB$BLOB_UTIL. * Fix Windows build. * Fix RDB$BLOB_UTIL.SEEK. * Fix crash. * Rework changing routines and names for better fit after creation of BLOB_APPEND. * Add RDB$BLOB_UTIL.IS_WRITABLE function. * Misc. * Fix documentation. * Re-add and use RDB$BLOB_UTIL_HANDLE domain. * Rename domain RDB$LONG_NUMBER to RDB$INTEGER. --- builds/win32/msvc15/engine_static.vcxproj | 2 + .../msvc15/engine_static.vcxproj.filters | 6 + doc/sql.extensions/README.blob_util.md | 199 ++++++++++++ src/include/firebird/impl/msg/jrd.h | 2 + src/include/gen/Firebird.pas | 2 + src/jrd/BlobUtil.cpp | 298 ++++++++++++++++++ src/jrd/BlobUtil.h | 127 ++++++++ src/jrd/SystemPackages.cpp | 2 + src/jrd/fields.h | 5 + src/jrd/jrd.h | 15 +- src/jrd/names.h | 5 + src/jrd/tra.cpp | 11 + src/jrd/tra.h | 4 + 13 files changed, 672 insertions(+), 6 deletions(-) create mode 100644 doc/sql.extensions/README.blob_util.md create mode 100644 src/jrd/BlobUtil.cpp create mode 100644 src/jrd/BlobUtil.h diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj index 72aa1a3029..d537e44fd6 100644 --- a/builds/win32/msvc15/engine_static.vcxproj +++ b/builds/win32/msvc15/engine_static.vcxproj @@ -58,6 +58,7 @@ + @@ -220,6 +221,7 @@ + diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters index e0f9907b7b..36d1aca2ef 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -216,6 +216,9 @@ JRD files + + JRD files + JRD files @@ -680,6 +683,9 @@ Header files + + Header files + Header files diff --git a/doc/sql.extensions/README.blob_util.md b/doc/sql.extensions/README.blob_util.md new file mode 100644 index 0000000000..a90fa3c4f0 --- /dev/null +++ b/doc/sql.extensions/README.blob_util.md @@ -0,0 +1,199 @@ +# `RDB$BLOB_UTIL` package (FB 5.0) + +This package exists to manipulate BLOBs in a way that standard Firebird functions, like `BLOB_APPEND` and `SUBSTRING` cannot do it or is very slow. + +These routines operates on binary data directly, even for text BLOBs. + +## Function `NEW_BLOB` + +`RDB$BLOB_UTIL.NEW_BLOB` is used to create a new BLOB. It returns a BLOB suitable for data appending, like `BLOB_APPEND` does. + +The advantage over `BLOB_APPEND` is that it's possible to set custom `SEGMENTED` and `TEMP_STORAGE` options. + +`BLOB_APPEND` always creates BLOB in temporary storage. That may not be the best approach if the created BLOB is going to be stored in a permanent table, as it will require copy. + +Returned BLOB from this function, even when `TEMP_STORAGE = FALSE` may be used with `BLOB_APPEND` for appending data. + +Input parameter: + - `SEGMENTED` type `BOOLEAN NOT NULL` + - `TEMP_STORAGE` type `BOOLEAN NOT NULL` + +Return type: `BLOB NOT NULL`. + +## Function `OPEN_BLOB` + +`RDB$BLOB_UTIL.OPEN_BLOB` is used to open an existing BLOB for read. It returns a handle (an integer bound to the transaction) suitable for use with others functions of this package, like `SEEK`, `READ_DATA` and `CLOSE_HANDLE`. + +Input parameter: + - `BLOB` type `BLOB NOT NULL` + +Return type: `INTEGER NOT NULL`. + +## Function `IS_WRITABLE` + +`RDB$BLOB_UTIL.IS_WRITABLE` returns `TRUE` when BLOB is suitable for data appending without copying using `BLOB_APPEND`. + +Input parameter: + - `BLOB` type `BLOB NOT NULL` + +Return type: `BOOLEAN NOT NULL`. + +## Function `READ_DATA` + +`RDB$BLOB_UTIL.READ_DATA` is used to read chunks of data of a BLOB handle opened with `RDB$BLOB_UTIL.OPEN_BLOB`. When the BLOB is fully read and there is no more data, it returns `NULL`. + +If `LENGTH` is passed with a positive number, it returns a VARBINARY with its maximum length. + +If `LENGTH` is `NULL` it returns just a segment of the BLOB with a maximum length of 32765. + +Input parameters: + - `HANDLE` type `INTEGER NOT NULL` + - `LENGTH` type `INTEGER` + +Return type: `VARBINARY(32767)`. + +## Function `SEEK` + +`RDB$BLOB_UTIL.SEEK` is used to set the position for the next `READ_DATA`. It returns the new position. + +`MODE` may be 0 (from the start), 1 (from current position) or 2 (from end). + +When `MODE` is 2, `OFFSET` should be zero or negative. + +Input parameter: + - `HANDLE` type `INTEGER NOT NULL` + - `MODE` type `INTEGER NOT NULL` + - `OFFSET` type `INTEGER NOT NULL` + +Return type: `INTEGER NOT NULL`. + +## Procedure `CANCEL_BLOB` + +`RDB$BLOB_UTIL.CANCEL_BLOB` is used to immediately release a temporary BLOB, like one created with `BLOB_APPEND`. + +Note that if the same BLOB is used after cancel, using the same variable or another one with the same BLOB id reference, invalid blob id error will be raised. + +## Procedure `CLOSE_HANDLE` + +`RDB$BLOB_UTIL.CLOSE_HANDLE` is used to close a BLOB handle opened with `RDB$BLOB_UTIL.OPEN_BLOB`. + +Not closed handles are closed automatically only in the transaction end. + +Input parameter: + - `HANDLE` type `INTEGER NOT NULL` + +# Examples + +- Example 1: Create a BLOB in temporary space and return it in `EXECUTE BLOCK`: + +``` +execute block returns (b blob) +as +begin + -- Create a BLOB handle in the temporary space. + b = rdb$blob_util.new_blob(false, true); + + -- Add chunks of data. + b = blob_append(b, '12345'); + b = blob_append(b, '67'); + + suspend; +end +``` + +- Example 2: Open a BLOB and return chunks of it with `EXECUTE BLOCK`: + +``` +execute block returns (s varchar(10)) +as + declare b blob = '1234567'; + declare bhandle integer; +begin + -- Open the BLOB and get a BLOB handle. + bhandle = rdb$blob_util.open_blob(b); + + -- Get chunks of data as string and return. + + s = rdb$blob_util.read_data(bhandle, 3); + suspend; + + s = rdb$blob_util.read_data(bhandle, 3); + suspend; + + s = rdb$blob_util.read_data(bhandle, 3); + suspend; + + -- Here EOF is found, so it returns NULL. + s = rdb$blob_util.read_data(bhandle, 3); + suspend; + + -- Close the BLOB handle. + execute procedure rdb$blob_util.close_handle(bhandle); +end +``` + +- Example 3: Seek in a blob. + +``` +set term !; + +execute block returns (s varchar(10)) +as + declare b blob; + declare bhandle integer; +begin + -- Create a stream BLOB handle. + b = rdb$blob_util.new_blob(false, true); + + -- Add data. + b = blob_append(b, '0123456789'); + + -- Open the BLOB. + bhandle = rdb$blob_util.open_blob(b); + + -- Seek to 5 since the start. + rdb$blob_util.seek(bhandle, 0, 5); + s = rdb$blob_util.read_data(bhandle, 3); + suspend; + + -- Seek to 2 since the start. + rdb$blob_util.seek(bhandle, 0, 2); + s = rdb$blob_util.read_data(bhandle, 3); + suspend; + + -- Advance 2. + rdb$blob_util.seek(bhandle, 1, 2); + s = rdb$blob_util.read_data(bhandle, 3); + suspend; + + -- Seek to -1 since the end. + rdb$blob_util.seek(bhandle, 2, -1); + s = rdb$blob_util.read_data(bhandle, 3); + suspend; +end! + +set term ;! +``` + +- Example 4: Check if blobs are writable: + +``` +create table t(b blob); + +set term !; + +execute block returns (bool boolean) +as + declare b blob; +begin + b = blob_append(null, 'writable'); + bool = rdb$blob_util.is_writable(b); + suspend; + + insert into t (b) values ('not writable') returning b into b; + bool = rdb$blob_util.is_writable(b); + suspend; +end! + +set term ;! +``` diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 1b8ea1a9fb..4ad003d2a2 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -961,3 +961,5 @@ FB_IMPL_MSG(JRD, 959, quoted_str_miss, -901, "22", "024", "Missing terminating q FB_IMPL_MSG(JRD, 960, wrong_shmem_ver, -902, "08", "006", "@1: inconsistent shared memory type/version; found @2, expected @3") FB_IMPL_MSG(JRD, 961, wrong_shmem_bitness, -902, "08", "006", "@1-bit engine can't open database already opened by @2-bit engine") FB_IMPL_MSG(JRD, 962, wrong_proc_plan, -281, "HY", "000", "Procedures cannot specify access type other than NATURAL in the plan") +FB_IMPL_MSG(JRD, 963, invalid_blob_util_handle, -402, "42", "000", "Invalid RDB$BLOB_UTIL handle") +FB_IMPL_MSG(JRD, 964, bad_temp_blob_id, -402, "42", "000", "Invalid temporary BLOB ID") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 20edfdc1b3..4778c4474e 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5302,6 +5302,8 @@ const isc_wrong_shmem_ver = 335545280; isc_wrong_shmem_bitness = 335545281; isc_wrong_proc_plan = 335545282; + isc_invalid_blob_util_handle = 335545283; + isc_bad_temp_blob_id = 335545284; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/BlobUtil.cpp b/src/jrd/BlobUtil.cpp new file mode 100644 index 0000000000..1dec99daf9 --- /dev/null +++ b/src/jrd/BlobUtil.cpp @@ -0,0 +1,298 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2020 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../jrd/BlobUtil.h" +#include "../jrd/blb.h" +#include "../jrd/tra.h" + +using namespace Jrd; +using namespace Firebird; + + +namespace +{ + blb* getBlobFromHandle(thread_db* tdbb, ISC_INT64 handle) + { + const auto transaction = tdbb->getTransaction(); + blb* blob; + + if (transaction->tra_blob_util_map.get(handle, blob)) + return blob; + else + status_exception::raise(Arg::Gds(isc_invalid_blob_util_handle)); + } + + BlobIndex* getTempBlobIndexFromId(thread_db* tdbb, const bid& blobId) + { + if (blobId.bid_internal.bid_relation_id) + return nullptr; + + const auto transaction = tdbb->getTransaction(); + + if (!transaction->tra_blobs->locate(blobId.bid_temp_id())) + status_exception::raise(Arg::Gds(isc_bad_segstr_id)); + + const auto blobIndex = &transaction->tra_blobs->current(); + fb_assert(blobIndex->bli_blob_object); + + return blobIndex; + } +} + +namespace Jrd { + +//-------------------------------------- + +IExternalResultSet* BlobUtilPackage::cancelBlobProcedure(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const BlobMessage::Type* in, void*) +{ + const auto tdbb = JRD_get_thread_data(); + const auto transaction = tdbb->getTransaction(); + + const auto blobId = *(bid*) &in->blob; + + if (const auto blobIdx = getTempBlobIndexFromId(tdbb, blobId)) + { + if (blobIdx->bli_materialized) + status_exception::raise(Arg::Gds(isc_bad_segstr_id)); + + const auto blob = blobIdx->bli_blob_object; + blob->BLB_cancel(tdbb); + + return nullptr; + } + else + status_exception::raise(Arg::Gds(isc_bad_temp_blob_id)); +} + +IExternalResultSet* BlobUtilPackage::closeHandleProcedure(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const HandleMessage::Type* in, void*) +{ + const auto tdbb = JRD_get_thread_data(); + const auto transaction = tdbb->getTransaction(); + const auto blob = getBlobFromHandle(tdbb, in->handle); + + transaction->tra_blob_util_map.remove(in->handle); + blob->BLB_close(tdbb); + + return nullptr; +} + +void BlobUtilPackage::isWritableFunction(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const BlobMessage::Type* in, BooleanMessage::Type* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto transaction = tdbb->getTransaction(); + + const auto blobId = *(bid*) &in->blob; + + out->booleanNull = FB_FALSE; + + if (const auto blobIdx = getTempBlobIndexFromId(tdbb, blobId)) + { + if (!blobIdx->bli_materialized && (blobIdx->bli_blob_object->blb_flags & BLB_close_on_read)) + { + out->boolean = FB_TRUE; + return; + } + } + + out->boolean = FB_FALSE; +} + +void BlobUtilPackage::newBlobFunction(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const NewBlobInput::Type* in, BlobMessage::Type* out) +{ + thread_db* tdbb = JRD_get_thread_data(); + const auto transaction = tdbb->getTransaction(); + + const UCHAR bpb[] = { + isc_bpb_version1, + isc_bpb_type, 1, UCHAR(in->segmented ? isc_bpb_type_segmented : isc_bpb_type_stream), + isc_bpb_storage, 1, UCHAR(in->tempStorage ? isc_bpb_storage_temp : isc_bpb_storage_main) + }; + + bid id; + blb* blob = blb::create2(tdbb, transaction, &id, sizeof(bpb), bpb); + + blob->blb_flags |= BLB_close_on_read; + + out->blobNull = FB_FALSE; + out->blob.gds_quad_low = (ULONG) blob->getTempId(); + out->blob.gds_quad_high = ((FB_UINT64) blob->getTempId()) >> 32; +} + +void BlobUtilPackage::openBlobFunction(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const BlobMessage::Type* in, HandleMessage::Type* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto transaction = tdbb->getTransaction(); + + const auto blobId = *(bid*) &in->blob; + const auto blob = blb::open(tdbb, transaction, &blobId); + + transaction->tra_blob_util_map.put(++transaction->tra_blob_util_next, blob); + + out->handleNull = FB_FALSE; + out->handle = transaction->tra_blob_util_next; +} + +void BlobUtilPackage::seekFunction(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const SeekInput::Type* in, SeekOutput::Type* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto transaction = tdbb->getTransaction(); + const auto blob = getBlobFromHandle(tdbb, in->handle); + + if (!(in->mode >= 0 && in->mode <= 2)) + status_exception::raise(Arg::Gds(isc_random) << "Seek mode must be 0 (START), 1 (CURRENT) or 2 (END)"); + + if (in->mode == 2 && in->offset > 0) // 2 == from END + { + status_exception::raise( + Arg::Gds(isc_random) << + "Argument OFFSET for RDB$BLOB_UTIL must be zero or negative when argument MODE is 2"); + } + + out->offsetNull = FB_FALSE; + out->offset = blob->BLB_lseek(in->mode, in->offset); +} + +void BlobUtilPackage::readDataFunction(ThrowStatusExceptionWrapper* status, + IExternalContext* context, const ReadDataInput::Type* in, BinaryMessage::Type* out) +{ + if (!in->lengthNull && in->length <= 0) + status_exception::raise(Arg::Gds(isc_random) << "Length must be NULL or greater than 0"); + + const auto tdbb = JRD_get_thread_data(); + const auto transaction = tdbb->getTransaction(); + const auto blob = getBlobFromHandle(tdbb, in->handle); + + if (in->lengthNull) + out->data.length = blob->BLB_get_segment(tdbb, (UCHAR*) out->data.str, sizeof(out->data.str)); + else + { + out->data.length = blob->BLB_get_data(tdbb, (UCHAR*) out->data.str, + MIN(in->length, sizeof(out->data.str)), false); + } + + out->dataNull = out->data.length == 0 && (blob->blb_flags & BLB_eof) ? FB_TRUE : FB_FALSE; +} + +//-------------------------------------- + + +BlobUtilPackage::BlobUtilPackage(Firebird::MemoryPool& pool) + : SystemPackage( + pool, + "RDB$BLOB_UTIL", + ODS_13_1, + // procedures + { + SystemProcedure( + pool, + "CANCEL_BLOB", + SystemProcedureFactory(), + prc_executable, + // input parameters + { + {"BLOB", fld_blob, false} + }, + // output parameters + {} + ), + SystemProcedure( + pool, + "CLOSE_HANDLE", + SystemProcedureFactory(), + prc_executable, + // input parameters + { + {"HANDLE", fld_butil_handle, false}, + }, + // output parameters + {} + ) + }, + // functions + { + SystemFunction( + pool, + "IS_WRITABLE", + SystemFunctionFactory(), + // parameters + { + {"BLOB", fld_blob, false} + }, + {fld_bool, false} + ), + SystemFunction( + pool, + "NEW_BLOB", + SystemFunctionFactory(), + // parameters + { + {"SEGMENTED", fld_bool, false}, + {"TEMP_STORAGE", fld_bool, false} + }, + {fld_blob, false} + ), + SystemFunction( + pool, + "OPEN_BLOB", + SystemFunctionFactory(), + // parameters + { + {"BLOB", fld_blob, false} + }, + {fld_butil_handle, false} + ), + SystemFunction( + pool, + "SEEK", + SystemFunctionFactory(), + // parameters + { + {"HANDLE", fld_butil_handle, false}, + {"MODE", fld_integer, false}, + {"OFFSET", fld_integer, false} + }, + {fld_integer, false} + ), + SystemFunction( + pool, + "READ_DATA", + SystemFunctionFactory(), + // parameters + { + {"HANDLE", fld_butil_handle, false}, + {"LENGTH", fld_integer, true} + }, + {fld_varybinary_max, true} + ) + } + ) +{ +} + +} // namespace Jrd diff --git a/src/jrd/BlobUtil.h b/src/jrd/BlobUtil.h new file mode 100644 index 0000000000..b26f596c6d --- /dev/null +++ b/src/jrd/BlobUtil.h @@ -0,0 +1,127 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2020 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef JRD_BLOB_UTIL_H +#define JRD_BLOB_UTIL_H + +#include "firebird.h" +#include "firebird/Message.h" +#include "../common/classes/fb_string.h" +#include "../common/classes/ImplementHelper.h" +#include "../common/status.h" +#include "../jrd/SystemPackages.h" + +namespace Jrd { + + +class BlobUtilPackage : public SystemPackage +{ +public: + BlobUtilPackage(Firebird::MemoryPool& pool); + +private: + FB_MESSAGE(BinaryMessage, Firebird::ThrowStatusExceptionWrapper, + (FB_INTL_VARCHAR(MAX_VARY_COLUMN_SIZE, 0), data) + ); + + FB_MESSAGE(BlobMessage, Firebird::ThrowStatusExceptionWrapper, + (FB_BLOB, blob) + ); + + FB_MESSAGE(HandleMessage, Firebird::ThrowStatusExceptionWrapper, + (FB_INTEGER, handle) + ); + + FB_MESSAGE(BooleanMessage, Firebird::ThrowStatusExceptionWrapper, + (FB_BOOLEAN, boolean) + ); + + //---------- + + static Firebird::IExternalResultSet* cancelBlobProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const BlobMessage::Type* in, void* out); + + //---------- + + static Firebird::IExternalResultSet* closeHandleProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const HandleMessage::Type* in, void* out); + + //---------- + + static void isWritableFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, + const BlobMessage::Type* in, BooleanMessage::Type* out); + + //---------- + + FB_MESSAGE(NewBlobInput, Firebird::ThrowStatusExceptionWrapper, + (FB_BOOLEAN, segmented) + (FB_BOOLEAN, tempStorage) + ); + + static void newBlobFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, + const NewBlobInput::Type* in, BlobMessage::Type* out); + + //---------- + + static void openBlobFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, + const BlobMessage::Type* in, HandleMessage::Type* out); + + //---------- + + FB_MESSAGE(SeekInput, Firebird::ThrowStatusExceptionWrapper, + (FB_INTEGER, handle) + (FB_INTEGER, mode) + (FB_INTEGER, offset) + ); + + FB_MESSAGE(SeekOutput, Firebird::ThrowStatusExceptionWrapper, + (FB_INTEGER, offset) + ); + + static void seekFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const SeekInput::Type* in, SeekOutput::Type* out); + + //---------- + + FB_MESSAGE(ReadDataInput, Firebird::ThrowStatusExceptionWrapper, + (FB_INTEGER, handle) + (FB_INTEGER, length) + ); + + static void readDataFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, + const ReadDataInput::Type* in, BinaryMessage::Type* out); + + //---------- + + static void makeBlobFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, + const HandleMessage::Type* in, BlobMessage::Type* out); +}; + + +} // namespace + +#endif // JRD_BLOB_UTIL_H diff --git a/src/jrd/SystemPackages.cpp b/src/jrd/SystemPackages.cpp index a6bf1ee627..bc4fd6a929 100644 --- a/src/jrd/SystemPackages.cpp +++ b/src/jrd/SystemPackages.cpp @@ -22,6 +22,7 @@ #include "firebird.h" #include "../jrd/SystemPackages.h" +#include "../jrd/BlobUtil.h" #include "../jrd/TimeZone.h" #include "../jrd/ProfilerManager.h" @@ -38,6 +39,7 @@ namespace { list->add(TimeZonePackage(pool)); list->add(ProfilerPackage(pool)); + list->add(BlobUtilPackage(pool)); } static InitInstance INSTANCE; diff --git a/src/jrd/fields.h b/src/jrd/fields.h index 16daf6292f..ae9818b354 100644 --- a/src/jrd/fields.h +++ b/src/jrd/fields.h @@ -227,3 +227,8 @@ FIELD(fld_short_description, nam_short_description, dtype_varying, 255 * METADATA_BYTES_PER_CHAR, dsc_text_type_metadata, NULL , true) FIELD(fld_seconds_interval, nam_seconds_interval, dtype_long, sizeof(SLONG) , 0 , NULL , true) FIELD(fld_prof_ses_id , nam_prof_ses_id , dtype_int64 , sizeof(SINT64) , 0 , NULL , true) + + FIELD(fld_butil_handle , nam_butil_handle , dtype_long , sizeof(SLONG) , 0 , NULL , true) + FIELD(fld_blob , nam_blob , dtype_blob , BLOB_SIZE , isc_blob_untyped , NULL , true) + FIELD(fld_varybinary_max, nam_varbinary_max , dtype_varying , MAX_VARY_COLUMN_SIZE , 0 , NULL , true) + FIELD(fld_integer , nam_integer , dtype_long , sizeof(SLONG) , 0 , NULL , true) diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index b715b56996..e9c97301e4 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -1147,15 +1147,18 @@ namespace Jrd { } } - EngineCheckout(Attachment* att, const char* from, bool optional = false) + EngineCheckout(Attachment* att, const char* from, Type type = REQUIRED) : m_tdbb(nullptr), m_from(from) { - fb_assert(optional || att); - - if (att && att->att_use_count) + if (type != AVOID) { - m_ref = att->getStable(); - m_ref->getSync()->leave(); + fb_assert(type == UNNECESSARY || att); + + if (att && att->att_use_count) + { + m_ref = att->getStable(); + m_ref->getSync()->leave(); + } } } diff --git a/src/jrd/names.h b/src/jrd/names.h index 2d6b48c1d0..3c1d599e76 100644 --- a/src/jrd/names.h +++ b/src/jrd/names.h @@ -466,3 +466,8 @@ NAME("MON$COMPILED_STATEMENT_ID", nam_mon_cmp_stmt_id) NAME("RDB$SHORT_DESCRIPTION", nam_short_description) NAME("RDB$SECONDS_INTERVAL", nam_seconds_interval) NAME("RDB$PROFILE_SESSION_ID", nam_prof_ses_id) + +NAME("RDB$BLOB_UTIL_HANDLE", nam_butil_handle) +NAME("RDB$BLOB", nam_blob) +NAME("RDB$VARBINARY_MAX", nam_varbinary_max) +NAME("RDB$INTEGER", nam_integer) diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index be7aaa3d19..3b449d57ce 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -1210,6 +1210,17 @@ void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction, Jrd::TraceTr if (!transaction->tra_outer) { + for (auto& item : transaction->tra_blob_util_map) + { + auto blb = item.second; + + // Let temporary blobs be cancelled in the block below. + if (!(blb->blb_flags & BLB_temporary)) + blb->BLB_close(tdbb); + } + + transaction->tra_blob_util_map.clear(); + if (transaction->tra_blobs->getFirst()) { while (true) diff --git a/src/jrd/tra.h b/src/jrd/tra.h index 76cf8b2f45..cd577f94e1 100644 --- a/src/jrd/tra.h +++ b/src/jrd/tra.h @@ -145,6 +145,7 @@ struct CallerName }; typedef Firebird::GenericMap > > ReplBlobMap; +typedef Firebird::GenericMap > > BlobUtilMap; const int DEFAULT_LOCK_TIMEOUT = -1; // infinite const char* const TRA_BLOB_SPACE = "fb_blob_"; @@ -174,6 +175,7 @@ public: tra_blobs(outer ? outer->tra_blobs : &tra_blobs_tree), tra_fetched_blobs(p), tra_repl_blobs(*p), + tra_blob_util_map(*p), tra_arrays(NULL), tra_deferred_job(NULL), tra_resources(*p), @@ -269,6 +271,7 @@ public: BlobIndexTree* tra_blobs; // pointer to actual list of active blobs FetchedBlobIdTree tra_fetched_blobs; // list of fetched blobs ReplBlobMap tra_repl_blobs; // map of blob IDs replicated in this transaction + BlobUtilMap tra_blob_util_map; // map of blob IDs for RDB$BLOB_UTIL package ArrayField* tra_arrays; // Linked list of active arrays Lock* tra_lock; // lock for transaction Lock* tra_alter_db_lock; // lock for ALTER DATABASE statement(s) @@ -298,6 +301,7 @@ public: SnapshotHandle tra_snapshot_handle; CommitNumber tra_snapshot_number; SortOwner tra_sorts; + SLONG tra_blob_util_next = 1; EDS::Transaction *tra_ext_common; //Transaction *tra_ext_two_phase;