public static void InitializeDatabaseFieldMetadata(QueryResultFieldMetadata *meta, MYSQL_FIELD *field, int fieldIndex) { meta->TableName = (IntPtr)field->org_table; meta->TableAlias = (IntPtr)field->table; meta->Name = (IntPtr)field->org_name; meta->Alias = (IntPtr)field->name; meta->FieldType = field->type; meta->Index = fieldIndex; meta->Type = MySqlTypeToFieldType(field->type); }
public QueryResult(IntPtr result, MYSQL_FIELD *fields, long rowCount, int fieldCount) { _result = result; _fields = fields; _rowCount = rowCount; _fieldCount = fieldCount; _fieldMetadata = (QueryResultFieldMetadata *)Marshal.AllocHGlobal(sizeof(QueryResultFieldMetadata) * _fieldCount); _currentRow = (Field *)Marshal.AllocHGlobal(sizeof(Field) * _fieldCount); for (int i = 0; i < _fieldCount; i++) { InitializeDatabaseFieldMetadata(&_fieldMetadata[i], &_fields[i], i); _currentRow[i].SetMetadata(&_fieldMetadata[i]); } }
void CleanUp() { if (_currentRow != default) { Marshal.FreeHGlobal((IntPtr)_currentRow); _currentRow = default; } if (_fieldMetadata != default) { Marshal.FreeHGlobal((IntPtr)_fieldMetadata); _fieldMetadata = default; } if (_result != default) { mysql_free_result(_result); _result = default; } }
public void SetMetadata(QueryResultFieldMetadata *fieldMeta) { _meta = fieldMeta; }
public const int UNSIGNED_FLAG = 32; /**< Field is unsigned */ public PreparedQueryResult(IntPtr stmt, IntPtr result, long rowCount, int fieldCount) { _stmt = stmt; _rowCount = rowCount; _fieldCount = fieldCount; _metadataResult = result; if (_metadataResult == default) { return; } if (DatabaseLoader.IsMySQL8) { if (((MYSQL_STMT *)_stmt)->bind_result_done != 0) { Marshal.FreeHGlobal((IntPtr)((MYSQL_STMT *)_stmt)->bind->length); Marshal.FreeHGlobal((IntPtr)((MYSQL_STMT *)_stmt)->bind->is_null); } } else { if (((MYSQL_STMT_OLD *)_stmt)->bind_result_done != 0) { Marshal.FreeHGlobal((IntPtr)((MYSQL_STMT_OLD *)_stmt)->bind->length); Marshal.FreeHGlobal((IntPtr)((MYSQL_STMT_OLD *)_stmt)->bind->is_null); } } _rBind = (MYSQL_BIND *)Marshal.AllocHGlobal(sizeof(MYSQL_BIND) * _fieldCount); //- for future readers wondering where the f**k this is freed - mysql_stmt_bind_result moves pointers to these // from m_rBind to m_stmt->bind and it is later freed by the `if (m_stmt->bind_result_done)` block just above here // MYSQL_STMT lifetime is equal to connection lifetime var isNullBuffer = (bool *)Marshal.AllocHGlobal(sizeof(bool) * _fieldCount); var lengthBuffer = (CULong *)Marshal.AllocHGlobal(sizeof(CULong) * _fieldCount); new Span <byte>(_rBind, sizeof(MYSQL_BIND) * _fieldCount).Fill(0); new Span <byte>(isNullBuffer, sizeof(bool) * _fieldCount).Fill(0); new Span <byte>(lengthBuffer, sizeof(CULong) * _fieldCount).Fill(0); //- This is where we store the (entire) resultset if (mysql_stmt_store_result(_stmt) != 0) { FEL_LOG_WARN("sql.sql", "{0}:mysql_stmt_store_result, cannot bind result from MySQL server. Error: {1}", "PreparedQueryResult()", mysql_stmt_error(_stmt)); Marshal.FreeHGlobal((IntPtr)_rBind); Marshal.FreeHGlobal((IntPtr)isNullBuffer); Marshal.FreeHGlobal((IntPtr)lengthBuffer); _rBind = default; return; } _rowCount = mysql_stmt_num_rows(_stmt); //- This is where we prepare the buffer based on metadata MYSQL_FIELD *field = mysql_fetch_fields(_metadataResult); _fieldMetadata = (QueryResultFieldMetadata *)Marshal.AllocHGlobal(sizeof(QueryResultFieldMetadata) * _fieldCount); int rowSize = 0; for (int i = 0; i < _fieldCount; ++i) { int size = SizeForType(&field[i]); rowSize += size; InitializeDatabaseFieldMetadata(&_fieldMetadata[i], &field[i], i); _rBind[i].buffer_type = field[i].type; _rBind[i].buffer_length = new CULong((uint)size); _rBind[i].length = &lengthBuffer[i]; _rBind[i].is_null = &isNullBuffer[i]; _rBind[i].error = default; _rBind[i].is_unsigned = (field[i].flags & UNSIGNED_FLAG) != 0; } var dataBuffer = (byte *)Marshal.AllocHGlobal(rowSize * (int)_rowCount); for (int i = 0, offset = 0; i < _fieldCount; ++i) { _rBind[i].buffer = dataBuffer + offset; offset += (int)_rBind[i].buffer_length.Value; } //- This is where we bind the bind the buffer to the statement if (mysql_stmt_bind_result(_stmt, _rBind)) { FEL_LOG_WARN("sql.sql", "{0}:mysql_stmt_bind_result, cannot bind result from MySQL server. Error: {1}", "PreparedQueryResult()", mysql_stmt_error(_stmt)); mysql_stmt_free_result(_stmt); CleanUp(); Marshal.FreeHGlobal((IntPtr)isNullBuffer); Marshal.FreeHGlobal((IntPtr)lengthBuffer); return; } _rows = (Field *)Marshal.AllocHGlobal(sizeof(Field) * _fieldCount * (int)_rowCount); while (_NextRow()) { for (int fIndex = 0; fIndex < _fieldCount; ++fIndex) { _rows[(int)_rowPosition * _fieldCount + fIndex].SetMetadata(&_fieldMetadata[fIndex]); var buffer_length = (int)_rBind[fIndex].buffer_length.Value; var fetched_length = (int)(*_rBind[fIndex].length).Value; if (!*_rBind[fIndex].is_null) { void *buffer = DatabaseLoader.IsMySQL8 ? ((MYSQL_STMT *)_stmt)->bind[fIndex].buffer : ((MYSQL_STMT_OLD *)_stmt)->bind[fIndex].buffer; switch (_rBind[fIndex].buffer_type) { case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_STRING: case MYSQL_TYPE_VAR_STRING: // warning - the string will not be null-terminated if there is no space for it in the buffer // 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 if (fetched_length < buffer_length) { *((byte *)buffer + fetched_length) = 0; } break; default: break; } _rows[(int)_rowPosition * _fieldCount + fIndex].SetByteValue((byte *)buffer, fetched_length); // move buffer pointer to next part if (DatabaseLoader.IsMySQL8) { ((MYSQL_STMT *)_stmt)->bind[fIndex].buffer = (byte *)buffer + rowSize; } else { ((MYSQL_STMT_OLD *)_stmt)->bind[fIndex].buffer = (byte *)buffer + rowSize; } } else { _rows[(int)_rowPosition * _fieldCount + fIndex].SetByteValue(default, (int)(*_rBind[fIndex].length).Value);