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;