mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 14:43:03 +01:00
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.
This commit is contained in:
parent
4c78ac43eb
commit
73c1ab807a
@ -58,6 +58,7 @@
|
||||
<ClCompile Include="..\..\..\src\jrd\Attachment.cpp" />
|
||||
<ClCompile Include="..\..\..\src\jrd\blb.cpp" />
|
||||
<ClCompile Include="..\..\..\src\jrd\blob_filter.cpp" />
|
||||
<ClCompile Include="..\..\..\src\jrd\BlobUtil.cpp" />
|
||||
<ClCompile Include="..\..\..\src\jrd\btn.cpp" />
|
||||
<ClCompile Include="..\..\..\src\jrd\btr.cpp" />
|
||||
<ClCompile Include="..\..\..\src\jrd\builtin.cpp" />
|
||||
@ -220,6 +221,7 @@
|
||||
<ClInclude Include="..\..\..\src\jrd\blb_proto.h" />
|
||||
<ClInclude Include="..\..\..\src\jrd\blf_proto.h" />
|
||||
<ClInclude Include="..\..\..\src\jrd\blob_filter.h" />
|
||||
<ClInclude Include="..\..\..\src\jrd\BlobUtil.h" />
|
||||
<ClInclude Include="..\..\..\src\jrd\blp.h" />
|
||||
<ClInclude Include="..\..\..\src\jrd\blr.h" />
|
||||
<ClInclude Include="..\..\..\src\jrd\btn.h" />
|
||||
|
@ -216,6 +216,9 @@
|
||||
<ClCompile Include="..\..\..\src\jrd\blob_filter.cpp">
|
||||
<Filter>JRD files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\jrd\BlobUtil.cpp">
|
||||
<Filter>JRD files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\src\jrd\btn.cpp">
|
||||
<Filter>JRD files</Filter>
|
||||
</ClCompile>
|
||||
@ -680,6 +683,9 @@
|
||||
<ClInclude Include="..\..\..\src\jrd\blob_filter.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\jrd\BlobUtil.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\src\jrd\blp.h">
|
||||
<Filter>Header files</Filter>
|
||||
</ClInclude>
|
||||
|
199
doc/sql.extensions/README.blob_util.md
Normal file
199
doc/sql.extensions/README.blob_util.md
Normal file
@ -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 ;!
|
||||
```
|
@ -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")
|
||||
|
@ -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;
|
||||
|
298
src/jrd/BlobUtil.cpp
Normal file
298
src/jrd/BlobUtil.cpp
Normal file
@ -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 <adrianosf@gmail.com>
|
||||
* 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<BlobMessage, VoidMessage, cancelBlobProcedure>(),
|
||||
prc_executable,
|
||||
// input parameters
|
||||
{
|
||||
{"BLOB", fld_blob, false}
|
||||
},
|
||||
// output parameters
|
||||
{}
|
||||
),
|
||||
SystemProcedure(
|
||||
pool,
|
||||
"CLOSE_HANDLE",
|
||||
SystemProcedureFactory<HandleMessage, VoidMessage, closeHandleProcedure>(),
|
||||
prc_executable,
|
||||
// input parameters
|
||||
{
|
||||
{"HANDLE", fld_butil_handle, false},
|
||||
},
|
||||
// output parameters
|
||||
{}
|
||||
)
|
||||
},
|
||||
// functions
|
||||
{
|
||||
SystemFunction(
|
||||
pool,
|
||||
"IS_WRITABLE",
|
||||
SystemFunctionFactory<BlobMessage, BooleanMessage, isWritableFunction>(),
|
||||
// parameters
|
||||
{
|
||||
{"BLOB", fld_blob, false}
|
||||
},
|
||||
{fld_bool, false}
|
||||
),
|
||||
SystemFunction(
|
||||
pool,
|
||||
"NEW_BLOB",
|
||||
SystemFunctionFactory<NewBlobInput, BlobMessage, newBlobFunction>(),
|
||||
// parameters
|
||||
{
|
||||
{"SEGMENTED", fld_bool, false},
|
||||
{"TEMP_STORAGE", fld_bool, false}
|
||||
},
|
||||
{fld_blob, false}
|
||||
),
|
||||
SystemFunction(
|
||||
pool,
|
||||
"OPEN_BLOB",
|
||||
SystemFunctionFactory<BlobMessage, HandleMessage, openBlobFunction>(),
|
||||
// parameters
|
||||
{
|
||||
{"BLOB", fld_blob, false}
|
||||
},
|
||||
{fld_butil_handle, false}
|
||||
),
|
||||
SystemFunction(
|
||||
pool,
|
||||
"SEEK",
|
||||
SystemFunctionFactory<SeekInput, SeekOutput, seekFunction>(),
|
||||
// parameters
|
||||
{
|
||||
{"HANDLE", fld_butil_handle, false},
|
||||
{"MODE", fld_integer, false},
|
||||
{"OFFSET", fld_integer, false}
|
||||
},
|
||||
{fld_integer, false}
|
||||
),
|
||||
SystemFunction(
|
||||
pool,
|
||||
"READ_DATA",
|
||||
SystemFunctionFactory<ReadDataInput, BinaryMessage, readDataFunction>(),
|
||||
// parameters
|
||||
{
|
||||
{"HANDLE", fld_butil_handle, false},
|
||||
{"LENGTH", fld_integer, true}
|
||||
},
|
||||
{fld_varybinary_max, true}
|
||||
)
|
||||
}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Jrd
|
127
src/jrd/BlobUtil.h
Normal file
127
src/jrd/BlobUtil.h
Normal file
@ -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 <adrianosf@gmail.com>
|
||||
* 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
|
@ -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<SystemPackagesInit> INSTANCE;
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -145,6 +145,7 @@ struct CallerName
|
||||
};
|
||||
|
||||
typedef Firebird::GenericMap<Firebird::Pair<Firebird::NonPooled<SINT64, ULONG> > > ReplBlobMap;
|
||||
typedef Firebird::GenericMap<Firebird::Pair<Firebird::NonPooled<SLONG, blb*> > > 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;
|
||||
|
Loading…
Reference in New Issue
Block a user