#2755: Fix MySQL's LONGBLOB/LONGTEXT not allocating enough space (#3474)

* Unit test for bug #2755.

* Removed condition to set buffer length to 0.

* Fixes to unit tests for LONGBLOB/TEXT data type.

* Adjusted buffer sizes to accommodate LONGBLOBs.

Co-authored-by: Hector Toledo Soto <hsoto@transperfect.com>
This commit is contained in:
hectots 2022-05-04 16:34:28 -04:00 committed by GitHub
parent 6ec184dfca
commit b1b97b9640
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 98 additions and 10 deletions

View File

@ -323,6 +323,8 @@ public:
private:
bool realExtractFixed(std::size_t pos, enum_field_types type, void* buffer, bool isUnsigned = false);
bool extractLongLOB(std::size_t pos);
// Prevent VC8 warning "operator= could not be generated"
Extractor& operator=(const Extractor&);

View File

@ -40,6 +40,9 @@ class ResultMetadata
/// MySQL result metadata
{
public:
~ResultMetadata();
/// Destroys the ResultMetadata.
void reset();
/// Resets the metadata.
@ -64,10 +67,13 @@ public:
bool isNull(std::size_t pos) const;
/// Returns true if value at pos is null.
void adjustColumnSizeToFit(std::size_t pos);
/// Expands the size allocated for column to fit the length of the data.
private:
std::vector<MetaColumn> _columns;
std::vector<MYSQL_BIND> _row;
std::vector<char> _buffer;
std::vector<char*> _buffer;
std::vector<unsigned long> _lengths;
std::vector<my_boolv> _isNull; // using char instead of bool to avoid std::vector<bool> disaster
};

View File

@ -147,6 +147,9 @@ bool Extractor::extract(std::size_t pos, Poco::Data::BLOB& val)
if (_metadata.metaColumn(static_cast<Poco::UInt32>(pos)).type() != Poco::Data::MetaColumn::FDT_BLOB)
throw MySQLException("Extractor: not a blob");
if (_metadata.metaColumn(static_cast<Poco::UInt32>(pos)).length() == 0 && !extractLongLOB(pos))
return false;
val.assignRaw(_metadata.rawData(pos), _metadata.length(pos));
return true;
}
@ -163,6 +166,9 @@ bool Extractor::extract(std::size_t pos, Poco::Data::CLOB& val)
if (_metadata.metaColumn(static_cast<Poco::UInt32>(pos)).type() != Poco::Data::MetaColumn::FDT_BLOB)
throw MySQLException("Extractor: not a blob");
if (_metadata.metaColumn(static_cast<Poco::UInt32>(pos)).length() == 0 && !extractLongLOB(pos))
return false;
val.assignRaw(reinterpret_cast<const char*>(_metadata.rawData(pos)), _metadata.length(pos));
return true;
}
@ -263,6 +269,22 @@ bool Extractor::realExtractFixed(std::size_t pos, enum_field_types type, void* b
return isNull == 0;
}
bool Extractor::extractLongLOB(std::size_t pos)
{
// Large LOBs (LONGBLOB and LONGTEXT) are fetched
// with a zero-length buffer to avoid allocating
// huge amounts of memory. Therefore, when extracting
// the buffers need to be adjusted.
_metadata.adjustColumnSizeToFit(pos);
MYSQL_BIND* row = _metadata.row();
if (!_stmt.fetchColumn(pos, &row[pos]))
return false;
return true;
}
//////////////
// Not implemented

View File

@ -140,6 +140,13 @@ namespace Data {
namespace MySQL {
ResultMetadata::~ResultMetadata()
{
for (std::vector<char*>::iterator it = _buffer.begin(); it != _buffer.end(); ++it)
std::free(*it);
}
void ResultMetadata::reset()
{
_columns.resize(0);
@ -165,7 +172,6 @@ void ResultMetadata::init(MYSQL_STMT* stmt)
std::size_t count = mysql_num_fields(h);
MYSQL_FIELD* fields = mysql_fetch_fields(h);
std::size_t commonSize = 0;
_columns.reserve(count);
for (std::size_t i = 0; i < count; i++)
@ -181,29 +187,24 @@ void ResultMetadata::init(MYSQL_STMT* stmt)
0, // TODO: precision
!IS_NOT_NULL(fields[i].flags) // nullable
));
commonSize += _columns[i].length();
}
_buffer.resize(commonSize);
_buffer.resize(count);
_row.resize(count);
_lengths.resize(count);
_isNull.resize(count);
std::size_t offset = 0;
for (std::size_t i = 0; i < count; i++)
{
std::memset(&_row[i], 0, sizeof(MYSQL_BIND));
unsigned int len = static_cast<unsigned int>(_columns[i].length());
_buffer[i] = (char*) std::calloc(len, sizeof(char));
_row[i].buffer_type = fields[i].type;
_row[i].buffer_length = len;
_row[i].buffer = (len > 0) ? (&_buffer[0] + offset) : 0;
_row[i].buffer = _buffer[i];
_row[i].length = &_lengths[i];
_row[i].is_null = reinterpret_cast<my_bool*>(&_isNull[i]); // workaround to make it work with both MySQL 8 and earlier
_row[i].is_unsigned = (fields[i].flags & UNSIGNED_FLAG) > 0;
offset += _row[i].buffer_length;
}
}
@ -244,4 +245,13 @@ bool ResultMetadata::isNull(std::size_t pos) const
}
void ResultMetadata::adjustColumnSizeToFit(std::size_t pos)
{
std::free(_buffer[pos]);
_buffer[pos] = (char*) std::calloc(_lengths[pos], sizeof(char));
_row[pos].buffer = _buffer[pos];
_row[pos].buffer_length = _lengths[pos];
}
} } } // namespace Poco::Data::MySQL

View File

@ -469,6 +469,15 @@ void MySQLTest::testBLOBStmt()
}
void MySQLTest::testLongBLOB()
{
if (!_pSession) fail ("Test not available.");
recreatePersonLongBLOBTable();
_pExecutor->longBlob();
}
void MySQLTest::testUnsignedInts()
{
if (!_pSession) fail ("Test not available.");
@ -752,6 +761,15 @@ void MySQLTest::recreatePersonTimeTable()
}
void MySQLTest::recreatePersonLongBLOBTable()
{
dropTable("Person");
try { *_pSession << "CREATE TABLE Person (LastName VARCHAR(30), FirstName VARCHAR(30), Address VARCHAR(30), Biography LONGTEXT)", now; }
catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail ("recreatePersonLongBLOBTable()"); }
catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail ("recreatePersonLongBLOBTable()"); }
}
void MySQLTest::recreateIntsTable()
{
dropTable("Strings");
@ -919,6 +937,7 @@ CppUnit::Test* MySQLTest::suite()
CppUnit_addTest(pSuite, MySQLTest, testDateTime);
//CppUnit_addTest(pSuite, MySQLTest, testBLOB);
CppUnit_addTest(pSuite, MySQLTest, testBLOBStmt);
CppUnit_addTest(pSuite, MySQLTest, testLongBLOB);
CppUnit_addTest(pSuite, MySQLTest, testUnsignedInts);
CppUnit_addTest(pSuite, MySQLTest, testFloat);
CppUnit_addTest(pSuite, MySQLTest, testDouble);

View File

@ -79,6 +79,7 @@ public:
void testDateTime();
void testBLOB();
void testBLOBStmt();
void testLongBLOB();
void testUnsignedInts();
void testFloat();
@ -117,6 +118,7 @@ private:
void recreatePersonDateTimeTable();
void recreatePersonDateTable();
void recreatePersonTimeTable();
void recreatePersonLongBLOBTable();
void recreateStringsTable();
void recreateIntsTable();
void recreateUnsignedIntsTable();

View File

@ -1435,6 +1435,32 @@ void SQLExecutor::blobStmt()
}
void SQLExecutor::longBlob()
{
std::string funct = "longBlob()";
std::string lastName("lastname");
std::string firstName("firstname");
std::string address("Address");
Poco::Data::CLOB biography("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 123);
int count = 0;
Statement ins = (*_pSession << "INSERT INTO Person VALUES (?,?,?,?)", use(lastName), use(firstName), use(address), use(biography));
ins.execute();
try { *_pSession << "SELECT COUNT(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); }
assertTrue (count == 1);
Poco::Data::CLOB res;
poco_assert (res.size() == 0);
Statement stmt = (*_pSession << "SELECT Biography FROM Person", into(res));
try { stmt.execute(); }
catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); }
poco_assert (res == biography);
}
void SQLExecutor::tuples()
{
typedef Tuple<int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int> TupleType;

View File

@ -83,6 +83,7 @@ public:
void dateTime();
void date();
void time();
void longBlob();
void unsignedInts();
void floats();
void doubles();