TrinityCore
QueryResult.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "QueryResult.h"
19#include "Errors.h"
20#include "Field.h"
22#include "Log.h"
23#include "MySQLHacks.h"
24#include "MySQLWorkaround.h"
25#include <cstring>
26
27namespace
28{
29static uint32 SizeForType(MYSQL_FIELD* field)
30{
31 switch (field->type)
32 {
33 case MYSQL_TYPE_NULL:
34 return 0;
35 case MYSQL_TYPE_TINY:
36 return 1;
37 case MYSQL_TYPE_YEAR:
38 case MYSQL_TYPE_SHORT:
39 return 2;
40 case MYSQL_TYPE_INT24:
41 case MYSQL_TYPE_LONG:
42 case MYSQL_TYPE_FLOAT:
43 return 4;
44 case MYSQL_TYPE_DOUBLE:
45 case MYSQL_TYPE_LONGLONG:
46 case MYSQL_TYPE_BIT:
47 return 8;
48
49 case MYSQL_TYPE_TIMESTAMP:
50 case MYSQL_TYPE_DATE:
51 case MYSQL_TYPE_TIME:
52 case MYSQL_TYPE_DATETIME:
53 return sizeof(MYSQL_TIME);
54
55 case MYSQL_TYPE_TINY_BLOB:
56 case MYSQL_TYPE_MEDIUM_BLOB:
57 case MYSQL_TYPE_LONG_BLOB:
58 case MYSQL_TYPE_BLOB:
59 case MYSQL_TYPE_STRING:
60 case MYSQL_TYPE_VAR_STRING:
61 return field->max_length + 1;
62
63 case MYSQL_TYPE_DECIMAL:
64 case MYSQL_TYPE_NEWDECIMAL:
65 return 64;
66
67 case MYSQL_TYPE_GEOMETRY:
68 /*
69 Following types are not sent over the wire:
70 MYSQL_TYPE_ENUM:
71 MYSQL_TYPE_SET:
72 */
73 default:
74 TC_LOG_WARN("sql.sql", "SQL::SizeForType(): invalid field type {}", uint32(field->type));
75 return 0;
76 }
77}
78
79DatabaseFieldTypes MysqlTypeToFieldType(enum_field_types type, uint32 flags)
80{
81 switch (type)
82 {
83 case MYSQL_TYPE_NULL:
85 case MYSQL_TYPE_TINY:
86 return (flags & UNSIGNED_FLAG) ? DatabaseFieldTypes::UInt8 : DatabaseFieldTypes::Int8;
87 case MYSQL_TYPE_YEAR:
88 case MYSQL_TYPE_SHORT:
90 case MYSQL_TYPE_INT24:
91 case MYSQL_TYPE_LONG:
93 case MYSQL_TYPE_LONGLONG:
94 case MYSQL_TYPE_BIT:
96 case MYSQL_TYPE_FLOAT:
98 case MYSQL_TYPE_DOUBLE:
100 case MYSQL_TYPE_DECIMAL:
101 case MYSQL_TYPE_NEWDECIMAL:
103 case MYSQL_TYPE_TIMESTAMP:
104 case MYSQL_TYPE_DATE:
105 case MYSQL_TYPE_TIME:
106 case MYSQL_TYPE_DATETIME:
108 case MYSQL_TYPE_TINY_BLOB:
109 case MYSQL_TYPE_MEDIUM_BLOB:
110 case MYSQL_TYPE_LONG_BLOB:
111 case MYSQL_TYPE_BLOB:
112 case MYSQL_TYPE_STRING:
113 case MYSQL_TYPE_VAR_STRING:
115 default:
116 TC_LOG_WARN("sql.sql", "MysqlTypeToFieldType(): invalid field type {}", uint32(type));
117 break;
118 }
119
121}
122
123static char const* FieldTypeToString(enum_field_types type, uint32 flags)
124{
125 switch (type)
126 {
127 case MYSQL_TYPE_BIT: return "BIT";
128 case MYSQL_TYPE_BLOB: return "BLOB";
129 case MYSQL_TYPE_DATE: return "DATE";
130 case MYSQL_TYPE_DATETIME: return "DATETIME";
131 case MYSQL_TYPE_NEWDECIMAL: return "NEWDECIMAL";
132 case MYSQL_TYPE_DECIMAL: return "DECIMAL";
133 case MYSQL_TYPE_DOUBLE: return "DOUBLE";
134 case MYSQL_TYPE_ENUM: return "ENUM";
135 case MYSQL_TYPE_FLOAT: return "FLOAT";
136 case MYSQL_TYPE_GEOMETRY: return "GEOMETRY";
137 case MYSQL_TYPE_INT24: return (flags & UNSIGNED_FLAG) ? "UNSIGNED INT24" : "INT24";
138 case MYSQL_TYPE_LONG: return (flags & UNSIGNED_FLAG) ? "UNSIGNED LONG" : "LONG";
139 case MYSQL_TYPE_LONGLONG: return (flags & UNSIGNED_FLAG) ? "UNSIGNED LONGLONG" : "LONGLONG";
140 case MYSQL_TYPE_LONG_BLOB: return "LONG_BLOB";
141 case MYSQL_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB";
142 case MYSQL_TYPE_NEWDATE: return "NEWDATE";
143 case MYSQL_TYPE_NULL: return "NULL";
144 case MYSQL_TYPE_SET: return "SET";
145 case MYSQL_TYPE_SHORT: return (flags & UNSIGNED_FLAG) ? "UNSIGNED SHORT" : "SHORT";
146 case MYSQL_TYPE_STRING: return "STRING";
147 case MYSQL_TYPE_TIME: return "TIME";
148 case MYSQL_TYPE_TIMESTAMP: return "TIMESTAMP";
149 case MYSQL_TYPE_TINY: return (flags & UNSIGNED_FLAG) ? "UNSIGNED TINY" : "TINY";
150 case MYSQL_TYPE_TINY_BLOB: return "TINY_BLOB";
151 case MYSQL_TYPE_VAR_STRING: return "VAR_STRING";
152 case MYSQL_TYPE_YEAR: return "YEAR";
153 default: return "-Unknown-";
154 }
155}
156
157std::unique_ptr<BaseDatabaseResultValueConverter> FromStringValueConverters[14] =
158{
159 nullptr,
160 std::make_unique<PrimitiveResultValueConverter<uint8, FromStringToDatabaseTypeConverter>>(),
162 std::make_unique<PrimitiveResultValueConverter<uint16, FromStringToDatabaseTypeConverter>>(),
164 std::make_unique<PrimitiveResultValueConverter<uint32, FromStringToDatabaseTypeConverter>>(),
166 std::make_unique<PrimitiveResultValueConverter<uint64, FromStringToDatabaseTypeConverter>>(),
168 std::make_unique<PrimitiveResultValueConverter<float, FromStringToDatabaseTypeConverter>>(),
170 std::make_unique<PrimitiveResultValueConverter<double, FromStringToDatabaseTypeConverter>>(),
171 nullptr,
172 std::make_unique<StringResultValueConverter>()
173};
174
175std::unique_ptr<BaseDatabaseResultValueConverter> BinaryValueConverters[14] =
176{
177 nullptr,
178 std::make_unique<PrimitiveResultValueConverter<uint8, FromBinaryToDatabaseTypeConverter>>(),
180 std::make_unique<PrimitiveResultValueConverter<uint16, FromBinaryToDatabaseTypeConverter>>(),
182 std::make_unique<PrimitiveResultValueConverter<uint32, FromBinaryToDatabaseTypeConverter>>(),
184 std::make_unique<PrimitiveResultValueConverter<uint64, FromBinaryToDatabaseTypeConverter>>(),
186 std::make_unique<PrimitiveResultValueConverter<float, FromBinaryToDatabaseTypeConverter>>(),
188 std::make_unique<PrimitiveResultValueConverter<double, FromStringToDatabaseTypeConverter>>(), // always sent as string
189 nullptr,
190 std::make_unique<StringResultValueConverter>()
191};
192
193void InitializeDatabaseFieldMetadata(QueryResultFieldMetadata* meta, MySQLField const* field, uint32 fieldIndex, bool binaryProtocol)
194{
195 meta->TableName = field->org_table;
196 meta->TableAlias = field->table;
197 meta->Name = field->org_name;
198 meta->Alias = field->name;
199 meta->TypeName = FieldTypeToString(field->type, field->flags);
200 meta->Index = fieldIndex;
201 meta->Type = MysqlTypeToFieldType(field->type, field->flags);
202 meta->Converter = binaryProtocol ? BinaryValueConverters[AsUnderlyingType(meta->Type)].get() : FromStringValueConverters[AsUnderlyingType(meta->Type)].get();
203}
204}
205
206ResultSet::ResultSet(MySQLResult* result, MySQLField* fields, uint64 rowCount, uint32 fieldCount) :
207_rowCount(rowCount),
208_fieldCount(fieldCount),
209_result(result),
210_fields(fields)
211{
214 for (uint32 i = 0; i < _fieldCount; i++)
215 {
216 InitializeDatabaseFieldMetadata(&_fieldMetadata[i], &_fields[i], i, false);
218 }
219}
220
222m_rowCount(rowCount),
223m_rowPosition(0),
224m_fieldCount(fieldCount),
225m_rBind(nullptr),
226m_stmt(stmt),
227m_metadataResult(result)
228{
229 if (!m_metadataResult)
230 return;
231
232 if (m_stmt->bind_result_done)
233 {
234 delete[] m_stmt->bind->length;
235 delete[] m_stmt->bind->is_null;
236 }
237
239
240 //- for future readers wondering where the fuck this is freed - mysql_stmt_bind_result moves pointers to these
241 // from m_rBind to m_stmt->bind and it is later freed by the `if (m_stmt->bind_result_done)` block just above here
242 // MYSQL_STMT lifetime is equal to connection lifetime
243 MySQLBool* m_isNull = new MySQLBool[m_fieldCount];
244 unsigned long* m_length = new unsigned long[m_fieldCount];
245
246 memset(m_isNull, 0, sizeof(MySQLBool) * m_fieldCount);
247 memset(m_rBind, 0, sizeof(MySQLBind) * m_fieldCount);
248 memset(m_length, 0, sizeof(unsigned long) * m_fieldCount);
249
250 //- This is where we store the (entire) resultset
251 if (mysql_stmt_store_result(m_stmt))
252 {
253 TC_LOG_WARN("sql.sql", "{}:mysql_stmt_store_result, cannot bind result from MySQL server. Error: {}", __FUNCTION__, mysql_stmt_error(m_stmt));
254 delete[] m_rBind;
255 delete[] m_isNull;
256 delete[] m_length;
257 return;
258 }
259
260 m_rowCount = mysql_stmt_num_rows(m_stmt);
261
262 //- This is where we prepare the buffer based on metadata
263 MySQLField* field = reinterpret_cast<MySQLField*>(mysql_fetch_fields(m_metadataResult));
265 std::size_t rowSize = 0;
266 for (uint32 i = 0; i < m_fieldCount; ++i)
267 {
268 uint32 size = SizeForType(&field[i]);
269 rowSize += size;
270
271 InitializeDatabaseFieldMetadata(&m_fieldMetadata[i], &field[i], i, true);
272
273 m_rBind[i].buffer_type = field[i].type;
274 m_rBind[i].buffer_length = size;
275 m_rBind[i].length = &m_length[i];
276 m_rBind[i].is_null = &m_isNull[i];
277 m_rBind[i].error = nullptr;
278 m_rBind[i].is_unsigned = field[i].flags & UNSIGNED_FLAG;
279 }
280
281 char* dataBuffer = new char[rowSize * m_rowCount];
282 for (uint32 i = 0, offset = 0; i < m_fieldCount; ++i)
283 {
284 m_rBind[i].buffer = dataBuffer + offset;
285 offset += m_rBind[i].buffer_length;
286 }
287
288 //- This is where we bind the bind the buffer to the statement
289 if (mysql_stmt_bind_result(m_stmt, m_rBind))
290 {
291 TC_LOG_WARN("sql.sql", "{}:mysql_stmt_bind_result, cannot bind result from MySQL server. Error: {}", __FUNCTION__, mysql_stmt_error(m_stmt));
292 mysql_stmt_free_result(m_stmt);
293 CleanUp();
294 delete[] m_isNull;
295 delete[] m_length;
296 return;
297 }
298
300 while (_NextRow())
301 {
302 for (uint32 fIndex = 0; fIndex < m_fieldCount; ++fIndex)
303 {
304 m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetMetadata(&m_fieldMetadata[fIndex]);
305
306 unsigned long buffer_length = m_rBind[fIndex].buffer_length;
307 unsigned long fetched_length = *m_rBind[fIndex].length;
308 if (!*m_rBind[fIndex].is_null)
309 {
310 void* buffer = m_stmt->bind[fIndex].buffer;
311 switch (m_rBind[fIndex].buffer_type)
312 {
313 case MYSQL_TYPE_TINY_BLOB:
314 case MYSQL_TYPE_MEDIUM_BLOB:
315 case MYSQL_TYPE_LONG_BLOB:
316 case MYSQL_TYPE_BLOB:
317 case MYSQL_TYPE_STRING:
318 case MYSQL_TYPE_VAR_STRING:
319 // warning - the string will not be null-terminated if there is no space for it in the buffer
320 // when mysql_stmt_fetch returned MYSQL_DATA_TRUNCATED
321 // we cannot blindly null-terminate the data either as it may be retrieved as binary blob and not specifically a string
322 // in this case using Field::GetCString will result in garbage
323 // TODO: remove Field::GetCString and use std::string_view in C++17
324 if (fetched_length < buffer_length)
325 *((char*)buffer + fetched_length) = '\0';
326 break;
327 default:
328 break;
329 }
330
331 m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetValue(
332 (char const*)buffer,
333 fetched_length);
334
335 // move buffer pointer to next part
336 m_stmt->bind[fIndex].buffer = (char*)buffer + rowSize;
337 }
338 else
339 {
340 m_rows[uint32(m_rowPosition) * m_fieldCount + fIndex].SetValue(
341 nullptr,
342 *m_rBind[fIndex].length);
343 }
344 }
346 }
347 m_rowPosition = 0;
348
350 mysql_stmt_free_result(m_stmt);
351}
352
354{
355 CleanUp();
356}
357
359{
360 CleanUp();
361}
362
364{
365 MYSQL_ROW row;
366
367 if (!_result)
368 return false;
369
370 row = mysql_fetch_row(_result);
371 if (!row)
372 {
373 CleanUp();
374 return false;
375 }
376
377 unsigned long* lengths = mysql_fetch_lengths(_result);
378 if (!lengths)
379 {
380 TC_LOG_WARN("sql.sql", "{}:mysql_fetch_lengths, cannot retrieve value lengths. Error {}.", __FUNCTION__, mysql_error(_result->handle));
381 CleanUp();
382 return false;
383 }
384
385 for (uint32 i = 0; i < _fieldCount; i++)
386 _currentRow[i].SetValue(row[i], lengths[i]);
387
388 return true;
389}
390
392{
395 if (++m_rowPosition >= m_rowCount)
396 return false;
397
398 return true;
399}
400
402{
406 return false;
407
408 int retval = mysql_stmt_fetch(m_stmt);
409 return retval == 0 || retval == MYSQL_DATA_TRUNCATED;
410}
411
413{
414 if (_currentRow)
415 {
416 delete [] _currentRow;
417 _currentRow = nullptr;
418 }
419
420 if (_result)
421 {
422 mysql_free_result(_result);
423 _result = nullptr;
424 }
425}
426
428{
430 mysql_free_result(m_metadataResult);
431
432 if (m_rBind)
433 {
434 delete[](char*)m_rBind->buffer;
435 delete[] m_rBind;
436 m_rBind = nullptr;
437 }
438}
439
440Field const& ResultSet::operator[](std::size_t index) const
441{
442 ASSERT(index < _fieldCount);
443 return _currentRow[index];
444}
445
447{
449 return const_cast<Field*>(&m_rows[uint32(m_rowPosition) * m_fieldCount]);
450}
451
452Field const& PreparedResultSet::operator[](std::size_t index) const
453{
455 ASSERT(index < m_fieldCount);
456 return m_rows[uint32(m_rowPosition) * m_fieldCount + index];
457}
uint64_t uint64
Definition: Define.h:141
uint32_t uint32
Definition: Define.h:142
uint16 flags
Definition: DisableMgr.cpp:49
#define ASSERT
Definition: Errors.h:68
DatabaseFieldTypes
Definition: Field.h:30
#define TC_LOG_WARN(filterType__,...)
Definition: Log.h:162
std::remove_pointer_t< decltype(std::declval< MYSQL_BIND >().is_null)> MySQLBool
Definition: MySQLHacks.h:32
constexpr std::underlying_type< E >::type AsUnderlyingType(E enumValue)
Definition: Util.h:491
Class used to access individual fields of database query result.
Definition: Field.h:90
void SetMetadata(QueryResultFieldMetadata const *meta)
Definition: Field.cpp:166
Field const & operator[](std::size_t index) const
std::vector< QueryResultFieldMetadata > m_fieldMetadata
Definition: QueryResult.h:67
MySQLStmt * m_stmt
Definition: QueryResult.h:75
uint64 m_rowPosition
Definition: QueryResult.h:70
PreparedResultSet(MySQLStmt *stmt, MySQLResult *result, uint64 rowCount, uint32 fieldCount)
MySQLBind * m_rBind
Definition: QueryResult.h:74
std::vector< Field > m_rows
Definition: QueryResult.h:68
Field * Fetch() const
MySQLResult * m_metadataResult
Field metadata, returned by mysql_stmt_result_metadata.
Definition: QueryResult.h:76
uint32 _fieldCount
Definition: QueryResult.h:42
Field * _currentRow
Definition: QueryResult.h:41
void CleanUp()
bool NextRow()
Field const & operator[](std::size_t index) const
MySQLField * _fields
Definition: QueryResult.h:47
std::vector< QueryResultFieldMetadata > _fieldMetadata
Definition: QueryResult.h:39
ResultSet(MySQLResult *result, MySQLField *fields, uint64 rowCount, uint32 fieldCount)
MySQLResult * _result
Definition: QueryResult.h:46
constexpr std::size_t size()
Definition: UpdateField.h:796
BaseDatabaseResultValueConverter const * Converter
Definition: Field.h:56
DatabaseFieldTypes Type
Definition: Field.h:55
char const * Alias
Definition: Field.h:52
char const * TableAlias
Definition: Field.h:50
char const * TableName
Definition: Field.h:49
char const * TypeName
Definition: Field.h:53
char const * Name
Definition: Field.h:51