bool _Query(PreparedStatementBase stmt, ref MySqlPreparedStatement?mysqlStmt, ref IntPtr pResult, ref long pRowCount, ref int pFieldCount) { if (_mysql == default) { return(false); } var index = stmt.Index; var mStmt = GetPreparedStatement(index); Assert(mStmt != null); // Can only be null if preparation failed, server side error or bad query mStmt !.BindParameters(stmt); mysqlStmt = mStmt; var msql_STMT = mStmt.STMT; MYSQL_BIND *msql_BIND = mStmt.Bind; var _s = GetMSTime(); if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) { var lErrno = mysql_errno(_mysql); FEL_LOG_ERROR("sql.sql", "SQL(p): {0}\n [ERROR]: [{1}] {2}", mStmt.GetQueryString(), lErrno, mysql_stmt_error(msql_STMT)); if (_HandleMySqlErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) { return(_Query(stmt, ref mysqlStmt, ref pResult, ref pRowCount, ref pFieldCount)); // Try again } mStmt.ClearParameters(); return(false); } if (mysql_stmt_execute(msql_STMT) != 0) { var lErrno = mysql_errno(_mysql); FEL_LOG_ERROR("sql.sql", "SQL(p): {0}\n [ERROR]: [{1}] {2}", mStmt.GetQueryString(), lErrno, mysql_stmt_error(msql_STMT)); if (_HandleMySqlErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) { return(_Query(stmt, ref mysqlStmt, ref pResult, ref pRowCount, ref pFieldCount)); // Try again } mStmt.ClearParameters(); return(false); } FEL_LOG_DEBUG("sql.sql", "[{0} ms] SQL(p): {1}", GetMSTimeDiff(_s, GetMSTime()), mStmt.GetQueryString()); mStmt.ClearParameters(); pResult = mysql_stmt_result_metadata(msql_STMT); pRowCount = mysql_stmt_num_rows(msql_STMT); pFieldCount = mysql_stmt_field_count(msql_STMT); return(true); }
public MySqlPreparedStatement(IntPtr stmt, string queryString) { _Mstmt = stmt; _queryString = queryString; _paramCount = mysql_stmt_param_count(stmt); _paramsSet = new bool[_paramCount]; _bind = (MYSQL_BIND *)Marshal.AllocHGlobal(sizeof(MYSQL_BIND) * _paramCount); new Span <byte>(_bind, sizeof(MYSQL_BIND) * _paramCount).Fill(0); // "If set to true, causes mysql_stmt_store_result() to update the metadata MYSQL_FIELD->max_length value." mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, true); }
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);