Core/DBLayer: Optimize string based query results by eliminating unneeded buffer copies

# Conflicts:
#	src/server/database/Database/Field.cpp
#	src/server/database/Database/Field.h
#	src/server/database/Database/QueryResult.cpp
This commit is contained in:
Shauren
2020-07-05 12:17:47 +02:00
committed by Ovahlord
parent 779c52ec7b
commit c51bd68b91
5 changed files with 123 additions and 135 deletions

View File

@@ -21,6 +21,7 @@
#include <future>
#include <memory>
struct QueryResultFieldMetadata;
class Field;
class ResultSet;

View File

@@ -17,19 +17,17 @@
#include "Field.h"
#include "Log.h"
#include "MySQLHacks.h"
Field::Field()
{
data.value = nullptr;
data.type = DatabaseFieldTypes::Null;
data.length = 0;
data.raw = false;
meta = nullptr;
}
Field::~Field()
{
CleanUp();
}
Field::~Field() = default;
uint8 Field::GetUInt8() const
{
@@ -46,8 +44,8 @@ uint8 Field::GetUInt8() const
#endif
if (data.raw)
return *reinterpret_cast<uint8*>(data.value);
return static_cast<uint8>(strtoul((char*)data.value, nullptr, 10));
return *reinterpret_cast<uint8 const*>(data.value);
return static_cast<uint8>(strtoul(data.value, nullptr, 10));
}
int8 Field::GetInt8() const
@@ -65,8 +63,8 @@ int8 Field::GetInt8() const
#endif
if (data.raw)
return *reinterpret_cast<int8*>(data.value);
return static_cast<int8>(strtol((char*)data.value, nullptr, 10));
return *reinterpret_cast<int8 const*>(data.value);
return static_cast<int8>(strtol(data.value, nullptr, 10));
}
uint16 Field::GetUInt16() const
@@ -84,8 +82,8 @@ uint16 Field::GetUInt16() const
#endif
if (data.raw)
return *reinterpret_cast<uint16*>(data.value);
return static_cast<uint16>(strtoul((char*)data.value, nullptr, 10));
return *reinterpret_cast<uint16 const*>(data.value);
return static_cast<uint16>(strtoul(data.value, nullptr, 10));
}
int16 Field::GetInt16() const
@@ -103,8 +101,8 @@ int16 Field::GetInt16() const
#endif
if (data.raw)
return *reinterpret_cast<int16*>(data.value);
return static_cast<int16>(strtol((char*)data.value, nullptr, 10));
return *reinterpret_cast<int16 const*>(data.value);
return static_cast<int16>(strtol(data.value, nullptr, 10));
}
uint32 Field::GetUInt32() const
@@ -122,8 +120,8 @@ uint32 Field::GetUInt32() const
#endif
if (data.raw)
return *reinterpret_cast<uint32*>(data.value);
return static_cast<uint32>(strtoul((char*)data.value, nullptr, 10));
return *reinterpret_cast<uint32 const*>(data.value);
return static_cast<uint32>(strtoul(data.value, nullptr, 10));
}
int32 Field::GetInt32() const
@@ -141,8 +139,8 @@ int32 Field::GetInt32() const
#endif
if (data.raw)
return *reinterpret_cast<int32*>(data.value);
return static_cast<int32>(strtol((char*)data.value, nullptr, 10));
return *reinterpret_cast<int32 const*>(data.value);
return static_cast<int32>(strtol(data.value, nullptr, 10));
}
uint64 Field::GetUInt64() const
@@ -160,8 +158,8 @@ uint64 Field::GetUInt64() const
#endif
if (data.raw)
return *reinterpret_cast<uint64*>(data.value);
return static_cast<uint64>(strtoull((char*)data.value, nullptr, 10));
return *reinterpret_cast<uint64 const*>(data.value);
return static_cast<uint64>(strtoull(data.value, nullptr, 10));
}
int64 Field::GetInt64() const
@@ -179,8 +177,8 @@ int64 Field::GetInt64() const
#endif
if (data.raw)
return *reinterpret_cast<int64*>(data.value);
return static_cast<int64>(strtoll((char*)data.value, nullptr, 10));
return *reinterpret_cast<int64 const*>(data.value);
return static_cast<int64>(strtoll(data.value, nullptr, 10));
}
float Field::GetFloat() const
@@ -198,8 +196,8 @@ float Field::GetFloat() const
#endif
if (data.raw)
return *reinterpret_cast<float*>(data.value);
return static_cast<float>(atof((char*)data.value));
return *reinterpret_cast<float const*>(data.value);
return static_cast<float>(atof(data.value));
}
double Field::GetDouble() const
@@ -217,8 +215,8 @@ double Field::GetDouble() const
#endif
if (data.raw && !IsType(DatabaseFieldTypes::Decimal))
return *reinterpret_cast<double*>(data.value);
return static_cast<double>(atof((char*)data.value));
return *reinterpret_cast<double const*>(data.value);
return static_cast<double>(atof(data.value));
}
char const* Field::GetCString() const
@@ -260,93 +258,44 @@ std::vector<uint8> Field::GetBinary() const
return result;
}
void Field::SetByteValue(void* newValue, DatabaseFieldTypes newType, uint32 length)
void Field::SetByteValue(char const* newValue, uint32 length)
{
// This value stores raw bytes that have to be explicitly cast later
data.value = newValue;
data.length = length;
data.type = newType;
data.raw = true;
}
void Field::SetStructuredValue(char* newValue, DatabaseFieldTypes newType, uint32 length)
void Field::SetStructuredValue(char const* newValue, uint32 length)
{
if (data.value)
CleanUp();
// This value stores somewhat structured data that needs function style casting
if (newValue)
{
data.value = new char[length + 1];
memcpy(data.value, newValue, length);
*(reinterpret_cast<char*>(data.value) + length) = '\0';
data.length = length;
}
data.type = newType;
data.value = newValue;
data.length = length;
data.raw = false;
}
bool Field::IsType(DatabaseFieldTypes type) const
{
return data.type == type;
return meta->Type == type;
}
bool Field::IsNumeric() const
{
return (data.type == DatabaseFieldTypes::Int8 ||
data.type == DatabaseFieldTypes::Int16 ||
data.type == DatabaseFieldTypes::Int32 ||
data.type == DatabaseFieldTypes::Int64 ||
data.type == DatabaseFieldTypes::Float ||
data.type == DatabaseFieldTypes::Double);
return (meta->Type == DatabaseFieldTypes::Int8 ||
meta->Type == DatabaseFieldTypes::Int16 ||
meta->Type == DatabaseFieldTypes::Int32 ||
meta->Type == DatabaseFieldTypes::Int64 ||
meta->Type == DatabaseFieldTypes::Float ||
meta->Type == DatabaseFieldTypes::Double);
}
#ifdef TRINITY_DEBUG
#include "MySQLHacks.h"
static char const* FieldTypeToString(enum_field_types type)
void Field::LogWrongType(char const* getter) const
{
switch (type)
{
case MYSQL_TYPE_BIT: return "BIT";
case MYSQL_TYPE_BLOB: return "BLOB";
case MYSQL_TYPE_DATE: return "DATE";
case MYSQL_TYPE_DATETIME: return "DATETIME";
case MYSQL_TYPE_NEWDECIMAL: return "NEWDECIMAL";
case MYSQL_TYPE_DECIMAL: return "DECIMAL";
case MYSQL_TYPE_DOUBLE: return "DOUBLE";
case MYSQL_TYPE_ENUM: return "ENUM";
case MYSQL_TYPE_FLOAT: return "FLOAT";
case MYSQL_TYPE_GEOMETRY: return "GEOMETRY";
case MYSQL_TYPE_INT24: return "INT24";
case MYSQL_TYPE_LONG: return "LONG";
case MYSQL_TYPE_LONGLONG: return "LONGLONG";
case MYSQL_TYPE_LONG_BLOB: return "LONG_BLOB";
case MYSQL_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB";
case MYSQL_TYPE_NEWDATE: return "NEWDATE";
case MYSQL_TYPE_NULL: return "NULL";
case MYSQL_TYPE_SET: return "SET";
case MYSQL_TYPE_SHORT: return "SHORT";
case MYSQL_TYPE_STRING: return "STRING";
case MYSQL_TYPE_TIME: return "TIME";
case MYSQL_TYPE_TIMESTAMP: return "TIMESTAMP";
case MYSQL_TYPE_TINY: return "TINY";
case MYSQL_TYPE_TINY_BLOB: return "TINY_BLOB";
case MYSQL_TYPE_VAR_STRING: return "VAR_STRING";
case MYSQL_TYPE_YEAR: return "YEAR";
default: return "-Unknown-";
}
TC_LOG_WARN("sql.sql", "Warning: %s on %s field %s.%s (%s.%s) at index %u.",
getter, meta->TypeName, meta->TableAlias, meta->Alias, meta->TableName, meta->Name, meta->Index);
}
void Field::SetMetadata(MySQLField* field, uint32 fieldIndex)
void Field::SetMetadata(QueryResultFieldMetadata const* fieldMeta)
{
meta.TableName = field->org_table;
meta.TableAlias = field->table;
meta.Name = field->org_name;
meta.Alias = field->name;
meta.Type = FieldTypeToString(field->type);
meta.Index = fieldIndex;
meta = fieldMeta;
}
#endif

View File

@@ -36,6 +36,17 @@ enum class DatabaseFieldTypes : uint8
Binary
};
struct QueryResultFieldMetadata
{
char const* TableName = nullptr;
char const* TableAlias = nullptr;
char const* Name = nullptr;
char const* Alias = nullptr;
char const* TypeName = nullptr;
uint32 Index = 0;
DatabaseFieldTypes Type = DatabaseFieldTypes::Null;
};
/**
@class Field
@@ -99,49 +110,25 @@ class TC_DATABASE_API Field
return data.value == nullptr;
}
#ifdef TRINITY_DEBUG
struct Metadata
{
char const* TableName;
char const* TableAlias;
char const* Name;
char const* Alias;
char const* Type;
uint32 Index;
};
#endif
protected:
#pragma pack(push, 1)
struct
{
uint32 length; // Length (prepared strings only)
void* value; // Actual data in memory
DatabaseFieldTypes type; // Field type
bool raw; // Raw bytes? (Prepared statement or ad hoc)
char const* value; // Actual data in memory
uint32 length; // Length
bool raw; // Raw bytes? (Prepared statement or ad hoc)
} data;
#pragma pack(pop)
void SetByteValue(void* newValue, DatabaseFieldTypes newType, uint32 length);
void SetStructuredValue(char* newValue, DatabaseFieldTypes newType, uint32 length);
void CleanUp()
{
// Field does not own the data if fetched with prepared statement
if (!data.raw)
delete[] ((char*)data.value);
data.value = nullptr;
}
void SetByteValue(char const* newValue, uint32 length);
void SetStructuredValue(char const* newValue, uint32 length);
bool IsType(DatabaseFieldTypes type) const;
bool IsNumeric() const;
private:
#ifdef TRINITY_DEBUG
void SetMetadata(MySQLField* field, uint32 fieldIndex);
Metadata meta;
#endif
QueryResultFieldMetadata const* meta;
void LogWrongType(char const* getter) const;
void SetMetadata(QueryResultFieldMetadata const* fieldMeta);
};
#endif

View File

@@ -22,6 +22,8 @@
#include "MySQLHacks.h"
#include "MySQLWorkaround.h"
namespace
{
static uint32 SizeForType(MYSQL_FIELD* field)
{
switch (field->type)
@@ -116,17 +118,65 @@ DatabaseFieldTypes MysqlTypeToFieldType(enum_field_types type)
return DatabaseFieldTypes::Null;
}
ResultSet::ResultSet(MySQLResult*result, MySQLField*fields, uint64 rowCount, uint32 fieldCount) :
static char const* FieldTypeToString(enum_field_types type)
{
switch (type)
{
case MYSQL_TYPE_BIT: return "BIT";
case MYSQL_TYPE_BLOB: return "BLOB";
case MYSQL_TYPE_DATE: return "DATE";
case MYSQL_TYPE_DATETIME: return "DATETIME";
case MYSQL_TYPE_NEWDECIMAL: return "NEWDECIMAL";
case MYSQL_TYPE_DECIMAL: return "DECIMAL";
case MYSQL_TYPE_DOUBLE: return "DOUBLE";
case MYSQL_TYPE_ENUM: return "ENUM";
case MYSQL_TYPE_FLOAT: return "FLOAT";
case MYSQL_TYPE_GEOMETRY: return "GEOMETRY";
case MYSQL_TYPE_INT24: return "INT24";
case MYSQL_TYPE_LONG: return "LONG";
case MYSQL_TYPE_LONGLONG: return "LONGLONG";
case MYSQL_TYPE_LONG_BLOB: return "LONG_BLOB";
case MYSQL_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB";
case MYSQL_TYPE_NEWDATE: return "NEWDATE";
case MYSQL_TYPE_NULL: return "NULL";
case MYSQL_TYPE_SET: return "SET";
case MYSQL_TYPE_SHORT: return "SHORT";
case MYSQL_TYPE_STRING: return "STRING";
case MYSQL_TYPE_TIME: return "TIME";
case MYSQL_TYPE_TIMESTAMP: return "TIMESTAMP";
case MYSQL_TYPE_TINY: return "TINY";
case MYSQL_TYPE_TINY_BLOB: return "TINY_BLOB";
case MYSQL_TYPE_VAR_STRING: return "VAR_STRING";
case MYSQL_TYPE_YEAR: return "YEAR";
default: return "-Unknown-";
}
}
void InitializeDatabaseFieldMetadata(QueryResultFieldMetadata* meta, MySQLField const* field, uint32 fieldIndex)
{
meta->TableName = field->org_table;
meta->TableAlias = field->table;
meta->Name = field->org_name;
meta->Alias = field->name;
meta->TypeName = FieldTypeToString(field->type);
meta->Index = fieldIndex;
meta->Type = MysqlTypeToFieldType(field->type);
}
}
ResultSet::ResultSet(MySQLResult* result, MySQLField* fields, uint64 rowCount, uint32 fieldCount) :
_rowCount(rowCount),
_fieldCount(fieldCount),
_result(result),
_fields(fields)
{
_fieldMetadata.resize(_fieldCount);
_currentRow = new Field[_fieldCount];
#ifdef TRINITY_DEBUG
for (uint32 i = 0; i < _fieldCount; i++)
_currentRow[i].SetMetadata(&_fields[i], i);
#endif
{
InitializeDatabaseFieldMetadata(&_fieldMetadata[i], &_fields[i], i);
_currentRow[i].SetMetadata(&_fieldMetadata[i]);
}
}
PreparedResultSet::PreparedResultSet(MySQLStmt* stmt, MySQLResult*result, uint64 rowCount, uint32 fieldCount) :
@@ -172,12 +222,15 @@ m_metadataResult(result)
//- This is where we prepare the buffer based on metadata
MySQLField* field = reinterpret_cast<MySQLField*>(mysql_fetch_fields(m_metadataResult));
m_fieldMetadata.resize(m_fieldCount);
std::size_t rowSize = 0;
for (uint32 i = 0; i < m_fieldCount; ++i)
{
uint32 size = SizeForType(&field[i]);
rowSize += size;
InitializeDatabaseFieldMetadata(&m_fieldMetadata[i], &field[i], i);
m_rBind[i].buffer_type = field[i].type;
m_rBind[i].buffer_length = size;
m_rBind[i].length = &m_length[i];
@@ -209,6 +262,8 @@ m_metadataResult(result)
{
for (uint32 fIndex = 0; fIndex < m_fieldCount; ++fIndex)
{
m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetMetadata(&m_fieldMetadata[fIndex]);
unsigned long buffer_length = m_rBind[fIndex].buffer_length;
unsigned long fetched_length = *m_rBind[fIndex].length;
if (!*m_rBind[fIndex].is_null)
@@ -226,7 +281,7 @@ m_metadataResult(result)
// when mysql_stmt_fetch returned MYSQL_DATA_TRUNCATED
// we cannot blindly null-terminate the data either as it may be retrieved as binary blob and not specifically a string
// in this case using Field::GetCString will result in garbage
// TODO: remove Field::GetCString and use boost::string_ref (currently proposed for TS as string_view, maybe in C++17)
// TODO: remove Field::GetCString and use std::string_view in C++17
if (fetched_length < buffer_length)
*((char*)buffer + fetched_length) = '\0';
break;
@@ -235,8 +290,7 @@ m_metadataResult(result)
}
m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetByteValue(
buffer,
MysqlTypeToFieldType(m_rBind[fIndex].buffer_type),
(char const*)buffer,
fetched_length);
// move buffer pointer to next part
@@ -246,13 +300,8 @@ m_metadataResult(result)
{
m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetByteValue(
nullptr,
MysqlTypeToFieldType(m_rBind[fIndex].buffer_type),
*m_rBind[fIndex].length);
}
#ifdef TRINITY_DEBUG
m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetMetadata(&field[fIndex], fIndex);
#endif
}
m_rowPosition++;
}
@@ -295,7 +344,7 @@ bool ResultSet::NextRow()
}
for (uint32 i = 0; i < _fieldCount; i++)
_currentRow[i].SetStructuredValue(row[i], MysqlTypeToFieldType( _fields[i].type), lengths[i]);
_currentRow[i].SetStructuredValue(row[i], lengths[i]);
return true;
}

View File

@@ -36,6 +36,7 @@ class TC_DATABASE_API ResultSet
Field const& operator[](std::size_t index) const;
protected:
std::vector<QueryResultFieldMetadata> _fieldMetadata;
uint64 _rowCount;
Field* _currentRow;
uint32 _fieldCount;
@@ -63,6 +64,7 @@ class TC_DATABASE_API PreparedResultSet
Field const& operator[](std::size_t index) const;
protected:
std::vector<QueryResultFieldMetadata> m_fieldMetadata;
std::vector<Field> m_rows;
uint64 m_rowCount;
uint64 m_rowPosition;