[odb-users] Re: custom type mapping using value_traits
specialization and memory leak prevention
Sten Kultakangas
ratkaisut at gmail.com
Sat May 2 16:00:02 EDT 2020
Hello
Answering my own question. I analyzed the source code mssql/statement.cxx
and came to the conclusion that everything we need is already provided so
we don't need any other state machine information. Here's the source code
of read_callback() registered by value_traits<string,
id_long_nstring>::set_value()
using Poco::UTF8Encoding;
using Poco::UTF16Encoding;
/*
SQLRETURN SQLGetData(
SQLHSTMT StatementHandle,
SQLUSMALLINT Col_or_Param_Num,
SQLSMALLINT TargetType,
SQLPOINTER TargetValuePtr,
SQLLEN BufferLength,
SQLLEN * StrLen_or_IndPtr);
After SQLGetData() is called with BufferLength=0, the callback function is
called for the first time with the 'chunk' parameter set to any of the
following
values:
- chunk_null, if 'StrLen_or_IndPtr' == SQL_NULL_DATA;
- chunk_one, if 'StrLen_or_IndPtr' == 0;
- chunk_first, otherwise.
Since BufferLength=0, the buffer does not contain any data yet.
Unless the 'chunk' was chunk_null, chunk_one or chunk_last, the callback
function must set the variables pointed to by 'buffer' and 'size'
parameters.
SQLGetData() will store at most 'size' bytes to the buffer.
*/
void value_traits<string, id_long_nstring>::read_callback(
void *context, // User context.
size_t *position, // Position context. An implementation is free
// to use this to track position information. It
// is initialized to zero before the first call.
void **buffer, // [in/out] Buffer to copy the data to. On the
// the first call it contains a pointer to the
// long_callback struct (used for redirections).
size_t *size, // [in/out] In: amount of data copied into the
// buffer after the previous call. Out: capacity
// of the buffer.
chunk_type chunk, // The position of this chunk; chunk_first means
// this is the first call, chunk_last means
there
// is no more data, chunk_null means this value
is
// NULL, and chunk_one means the value is empty.
size_t size_left, // Contains the amount of data left or 0 if this
// information is not available.
void *tmp_buffer, // A temporary buffer that may be used by the
// implementation.
size_t tmp_capacity // Capacity of the temporary buffer.
)
{
string &result(*static_cast<string *>(context));
if(chunk == chunk_null || chunk_one) {
result.clear();
return;
}
if(chunk == chunk_first) {
*buffer = tmp_buffer;
*size = tmp_capacity;
return;
}
/* Convert at most
* (char *)(*buffer) + *size - (char *)tmp_buffer
* bytes containing the UTF16 character sequence from 'tmp_buffer' and
* append the resulting UTF8 characters to the 'result'. If the sequence
* is truncated, move the unconverted bytes to the beginning of the
* 'tmp_buffer', increase the pointer stored in the variable pointed to
* by 'buffer' by the number of bytes containing the unconverted UTF16
* characters and decrease the variable pointed to by 'size' by the said
* number.
*/
UTF8Encoding utf8;
UTF16Encoding utf16(UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER);
size_t chunk_left = (char *)(*buffer) + *size - (char *)tmp_buffer;
auto chunk_p = (unsigned char *)(tmp_buffer);
while(chunk_left != 0) {
auto char_size = utf16.sequenceLength(chunk_p, chunk_left);
if(char_size <= 0) break;
if((size_t)char_size > chunk_left) break;
auto ch = utf16.queryConvert(chunk_p, chunk_left);
unsigned char utf8_char[6];
auto utf8_char_size = utf8.convert(ch, utf8_char,
sizeof(utf8_char));
result.append((char *)utf8_char, utf8_char_size);
chunk_p += char_size;
chunk_left -= char_size;
}
if(chunk == chunk_last) {
if(chunk_left == 0) return;
Scope;
Error << "nvarchar contains truncated data, bytes left " <<
chunk_left;
return;
}
if(chunk_left != 0) memmove(tmp_buffer, chunk_p, chunk_left);
*buffer = (char *)tmp_buffer + chunk_left;
*size = tmp_capacity - chunk_left;
}
On Sat, May 2, 2020 at 6:50 PM Sten Kultakangas <ratkaisut at gmail.com> wrote:
> Hello
>
> I am implementing nvarchar <-> utf8-encoded std::string database type
> mapping. Everything seems to be easy for nvarchar fields not exceeding
> certain limit in length:
>
> #include <cstring>
> #include <Poco/UnicodeConverter.h>
> #include "core/db_types.h"
>
> using Poco::UnicodeConverter;
> using namespace std;
>
> namespace odb {
> namespace mssql {
>
> void value_traits<string, id_nstring>::set_value(string &value,
> const ucs2_char *buffer, size_t buffer_size, bool is_null)
> {
> if(is_null) value = "";
> else UnicodeConverter::convert(buffer, buffer_size, value);
> }
>
>
> void value_traits<string, id_nstring>::set_image(ucs2_char *buffer,
> size_t buffer_size, size_t &actual_size, bool &is_null, const string
> &value)
> {
> Poco::UTF16String utf16;
> UnicodeConverter::convert(value, utf16);
>
> is_null = false;
> actual_size = utf16.size();
> if(actual_size > buffer_size) actual_size = buffer_size;
> memcpy(buffer, utf16.data(), actual_size * sizeof(ucs2_char));
> }
>
> }
> }
>
> However, i would like to implement the specialization for the
> id_long_nstring type also so i can work with nvarchar fields exceeding the
> length limit. The main concern is whether the callback is called with
> chunk_type=chunk_last even in the case of an exception thrown due to an I/O
> error. I could not find any destructor to prove that the callback will be
> called in such scenario. If the callback is not called in the case of an
> I/O error, the non-trivially destructible object referenced in the "user
> context" parameter will not be destroyed and a memory leak will occur. If
> there is such a limitation, then i must design a trivially destructible
> state machine object and place it in the provided buffer to prevent memory
> leaks.
>
> Can you confirm my concern that the callback will not be called in the
> case of an exception thrown during the set_value/set_image operation for
> the id_long_nstring type ?
>
> Best regards,
> Sten Kultakangas
>
>
More information about the odb-users
mailing list