public static int SizeForType(MYSQL_FIELD *field) { switch (field->type) { case MYSQL_TYPE_NULL: return(0); case MYSQL_TYPE_TINY: return(1); case MYSQL_TYPE_YEAR: case MYSQL_TYPE_SHORT: return(2); case MYSQL_TYPE_INT24: case MYSQL_TYPE_LONG: case MYSQL_TYPE_FLOAT: return(4); case MYSQL_TYPE_DOUBLE: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_BIT: return(8); case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: return(sizeof(MYSQL_TIME)); 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: return((int)(field->max_length).Value + 1); case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_NEWDECIMAL: return(64); case MYSQL_TYPE_GEOMETRY: /* * Following types are not sent over the wire: * MYSQL_TYPE_ENUM: * MYSQL_TYPE_SET: */ default: FEL_LOG_WARN("sql.sql", "SQL::SizeForType(): invalid field type {0}", field->type); return(0); } }
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]); } }
bool _Query(string sql, ref IntPtr pResult, ref MYSQL_FIELD *pFields, ref long pRowCount, ref int pFieldCount) { if (_mysql == IntPtr.Zero) { return(false); } { var _s = GetMSTime(); if (mysql_query(_mysql, sql) != 0) { var lErrno = mysql_errno(_mysql); FEL_LOG_INFO("sql.sql", "SQL: {0}", sql); FEL_LOG_ERROR("sql.sql", "[{0}] {1}", lErrno, mysql_error(_mysql)); if (_HandleMySqlErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) { return(_Query(sql, ref pResult, ref pFields, ref pRowCount, ref pFieldCount)); // We try again } return(false); } else { FEL_LOG_DEBUG("sql.sql", "[{0} ms] SQL: {1}", GetMSTimeDiff(_s, GetMSTime()), sql); } pResult = mysql_store_result(_mysql); pRowCount = mysql_affected_rows(_mysql); pFieldCount = mysql_field_count(_mysql); } if ((IntPtr)pResult == IntPtr.Zero) { return(false); } if (pRowCount == 0) { mysql_free_result(pResult); return(false); } pFields = mysql_fetch_fields(pResult); return(true); }
public QueryResult?Query(string sql) { if (string.IsNullOrEmpty(sql)) { return(null); } IntPtr result = default; MYSQL_FIELD *fields = default; long rowCount = 0; int fieldCount = 0; if (!_Query(sql, ref result, ref fields, ref rowCount, ref fieldCount)) { return(null); } return(new QueryResult(result, fields, rowCount, fieldCount)); }
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);
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); }