public async Task <ResultSet> ReadResultSetHeaderAsync(IOBehavior ioBehavior) { // ResultSet can be re-used, so initialize everything BufferState = ResultSetState.None; ColumnDefinitions = null; ColumnTypes = null; LastInsertId = 0; RecordsAffected = null; State = ResultSetState.None; m_columnDefinitionPayloadUsedBytes = 0; m_dataLengths = null; m_dataOffsets = null; m_readBuffer.Clear(); m_row = null; m_rowBuffered = null; m_hasRows = false; try { while (true) { var payload = await Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); var firstByte = payload.HeaderByte; if (firstByte == OkPayload.Signature) { var ok = OkPayload.Create(payload); RecordsAffected = (RecordsAffected ?? 0) + ok.AffectedRowCount; LastInsertId = unchecked ((long)ok.LastInsertId); if (ok.NewSchema != null) { Connection.Session.DatabaseOverride = ok.NewSchema; } ColumnDefinitions = null; ColumnTypes = null; State = (ok.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData; if (State == ResultSetState.NoMoreData) { break; } } else if (firstByte == LocalInfilePayload.Signature) { try { var localInfile = LocalInfilePayload.Create(payload); if (!IsHostVerified(Connection) && !localInfile.FileName.StartsWith(MySqlBulkLoader.StreamPrefix, StringComparison.Ordinal)) { throw new NotSupportedException("Use SourceStream or SslMode >= VerifyCA for LOAD DATA LOCAL INFILE"); } using (var stream = localInfile.FileName.StartsWith(MySqlBulkLoader.StreamPrefix, StringComparison.Ordinal) ? MySqlBulkLoader.GetAndRemoveStream(localInfile.FileName) : File.OpenRead(localInfile.FileName)) { byte[] readBuffer = new byte[65536]; int byteCount; while ((byteCount = await stream.ReadAsync(readBuffer, 0, readBuffer.Length).ConfigureAwait(false)) > 0) { payload = new PayloadData(new ArraySegment <byte>(readBuffer, 0, byteCount)); await Session.SendReplyAsync(payload, ioBehavior, CancellationToken.None).ConfigureAwait(false); } } } catch (Exception ex) { // store the exception, to be thrown after reading the response packet from the server ReadResultSetHeaderException = new MySqlException("Error during LOAD DATA LOCAL INFILE", ex); } await Session.SendReplyAsync(EmptyPayload.Create(), ioBehavior, CancellationToken.None).ConfigureAwait(false); } else { int ReadColumnCount(ArraySegment <byte> arraySegment) { var reader = new ByteArrayReader(arraySegment); var columnCount_ = (int)reader.ReadLengthEncodedInteger(); if (reader.BytesRemaining != 0) { throw new MySqlException("Unexpected data at end of column_count packet; see https://github.com/mysql-net/MySqlConnector/issues/324"); } return(columnCount_); } var columnCount = ReadColumnCount(payload.ArraySegment); // reserve adequate space to hold a copy of all column definitions (but note that this can be resized below if we guess too small) Array.Resize(ref m_columnDefinitionPayloads, columnCount * 96); ColumnDefinitions = new ColumnDefinitionPayload[columnCount]; ColumnTypes = new MySqlDbType[columnCount]; m_dataOffsets = new int[columnCount]; m_dataLengths = new int[columnCount]; for (var column = 0; column < ColumnDefinitions.Length; column++) { payload = await Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); var arraySegment = payload.ArraySegment; // 'Session.ReceiveReplyAsync' reuses a shared buffer; make a copy so that the column definitions can always be safely read at any future point if (m_columnDefinitionPayloadUsedBytes + arraySegment.Count > m_columnDefinitionPayloads.Length) { Array.Resize(ref m_columnDefinitionPayloads, Math.Max(m_columnDefinitionPayloadUsedBytes + arraySegment.Count, m_columnDefinitionPayloadUsedBytes * 2)); } Buffer.BlockCopy(arraySegment.Array, arraySegment.Offset, m_columnDefinitionPayloads, m_columnDefinitionPayloadUsedBytes, arraySegment.Count); var columnDefinition = ColumnDefinitionPayload.Create(new ArraySegment <byte>(m_columnDefinitionPayloads, m_columnDefinitionPayloadUsedBytes, arraySegment.Count)); ColumnDefinitions[column] = columnDefinition; ColumnTypes[column] = TypeMapper.ConvertToMySqlDbType(columnDefinition, treatTinyAsBoolean: Connection.TreatTinyAsBoolean, guidFormat: Connection.GuidFormat); m_columnDefinitionPayloadUsedBytes += arraySegment.Count; } if (!Session.SupportsDeprecateEof) { payload = await Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); EofPayload.Create(payload); } LastInsertId = -1; State = ResultSetState.ReadResultSetHeader; break; } } } catch (Exception ex) { ReadResultSetHeaderException = ex; } finally { BufferState = State; } return(this); }
private PayloadData CreateQueryPayload(PreparedStatement preparedStatement, MySqlParameterCollection parameterCollection, MySqlGuidFormat guidFormat) { var writer = new ByteBufferWriter(); writer.Write((byte)CommandKind.StatementExecute); writer.Write(preparedStatement.StatementId); writer.Write((byte)0); writer.Write(1); if (preparedStatement.Parameters?.Length > 0) { // TODO: How to handle incorrect number of parameters? // build subset of parameters for this statement var parameters = new MySqlParameter[preparedStatement.Statement.ParameterNames.Count]; for (var i = 0; i < preparedStatement.Statement.ParameterNames.Count; i++) { var parameterName = preparedStatement.Statement.ParameterNames[i]; var parameterIndex = parameterName != null ? (parameterCollection?.NormalizedIndexOf(parameterName) ?? -1) : preparedStatement.Statement.ParameterIndexes[i]; if (parameterIndex == -1 && parameterName != null) { throw new MySqlException("Parameter '{0}' must be defined.".FormatInvariant(parameterName)); } else if (parameterIndex < 0 || parameterIndex >= (parameterCollection?.Count ?? 0)) { throw new MySqlException("Parameter index {0} is invalid when only {1} parameter{2} defined.".FormatInvariant(parameterIndex, parameterCollection?.Count ?? 0, parameterCollection?.Count == 1 ? " is" : "s are")); } parameters[i] = parameterCollection[parameterIndex]; } // write null bitmap byte nullBitmap = 0; for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; if (parameter.Value is null || parameter.Value == DBNull.Value) { nullBitmap |= (byte)(1 << (i % 8)); } if (i % 8 == 7) { writer.Write(nullBitmap); nullBitmap = 0; } } if (parameters.Length % 8 != 0) { writer.Write(nullBitmap); } // write "new parameters bound" flag writer.Write((byte)1); foreach (var parameter in parameters) { writer.Write(TypeMapper.ConvertToColumnTypeAndFlags(parameter.MySqlDbType, guidFormat)); } var options = m_command.CreateStatementPreparerOptions(); foreach (var parameter in parameters) { parameter.AppendBinary(writer, options); } } return(writer.ToPayloadData()); }
public async Task ReadResultSetHeaderAsync(IOBehavior ioBehavior) { Reset(); try { while (true) { var payload = await Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); var firstByte = payload.HeaderByte; if (firstByte == OkPayload.Signature) { var ok = OkPayload.Create(payload.Span, Session.SupportsDeprecateEof, Session.SupportsSessionTrack); RecordsAffected = (RecordsAffected ?? 0) + ok.AffectedRowCount; LastInsertId = unchecked ((long)ok.LastInsertId); WarningCount = ok.WarningCount; if (ok.NewSchema is not null) { Connection.Session.DatabaseOverride = ok.NewSchema; } ColumnDefinitions = null; ColumnTypes = null; State = (ok.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData; if (State == ResultSetState.NoMoreData) { break; } } else if (firstByte == LocalInfilePayload.Signature) { try { if (!Connection.AllowLoadLocalInfile) { throw new NotSupportedException("To use LOAD DATA LOCAL INFILE, set AllowLoadLocalInfile=true in the connection string. See https://fl.vu/mysql-load-data"); } var localInfile = LocalInfilePayload.Create(payload.Span); var hasSourcePrefix = localInfile.FileName.StartsWith(MySqlBulkLoader.SourcePrefix, StringComparison.Ordinal); if (!IsHostVerified(Connection) && !hasSourcePrefix) { throw new NotSupportedException("Use SourceStream or SslMode >= VerifyCA for LOAD DATA LOCAL INFILE. See https://fl.vu/mysql-load-data"); } var source = hasSourcePrefix ? MySqlBulkLoader.GetAndRemoveSource(localInfile.FileName) : File.OpenRead(localInfile.FileName); switch (source) { case Stream stream: var buffer = ArrayPool <byte> .Shared.Rent(1048576); try { int byteCount; while ((byteCount = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) { payload = new(new ArraySegment <byte>(buffer, 0, byteCount)); await Session.SendReplyAsync(payload, ioBehavior, CancellationToken.None).ConfigureAwait(false); } } finally { ArrayPool <byte> .Shared.Return(buffer); stream.Dispose(); } break; case MySqlBulkCopy bulkCopy: await bulkCopy.SendDataReaderAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); break; default: throw new InvalidOperationException("Unsupported Source type: {0}".FormatInvariant(source.GetType().Name)); } } catch (Exception ex) { // store the exception, to be thrown after reading the response packet from the server ReadResultSetHeaderException = new("Error during LOAD DATA LOCAL INFILE", ex); } await Session.SendReplyAsync(EmptyPayload.Instance, ioBehavior, CancellationToken.None).ConfigureAwait(false); } else { int ReadColumnCount(ReadOnlySpan <byte> span) { var reader = new ByteArrayReader(span); var columnCount_ = (int)reader.ReadLengthEncodedInteger(); if (reader.BytesRemaining != 0) { throw new MySqlException("Unexpected data at end of column_count packet; see https://github.com/mysql-net/MySqlConnector/issues/324"); } return(columnCount_); } var columnCount = ReadColumnCount(payload.Span); // reserve adequate space to hold a copy of all column definitions (but note that this can be resized below if we guess too small) Utility.Resize(ref m_columnDefinitionPayloads, columnCount * 96); ColumnDefinitions = new ColumnDefinitionPayload[columnCount]; ColumnTypes = new MySqlDbType[columnCount]; for (var column = 0; column < ColumnDefinitions.Length; column++) { payload = await Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); var payloadLength = payload.Span.Length; // 'Session.ReceiveReplyAsync' reuses a shared buffer; make a copy so that the column definitions can always be safely read at any future point if (m_columnDefinitionPayloadUsedBytes + payloadLength > m_columnDefinitionPayloads.Count) { Utility.Resize(ref m_columnDefinitionPayloads, m_columnDefinitionPayloadUsedBytes + payloadLength); } payload.Span.CopyTo(m_columnDefinitionPayloads.Array.AsSpan().Slice(m_columnDefinitionPayloadUsedBytes)); var columnDefinition = ColumnDefinitionPayload.Create(new ResizableArraySegment <byte>(m_columnDefinitionPayloads, m_columnDefinitionPayloadUsedBytes, payloadLength)); ColumnDefinitions[column] = columnDefinition; ColumnTypes[column] = TypeMapper.ConvertToMySqlDbType(columnDefinition, treatTinyAsBoolean: Connection.TreatTinyAsBoolean, guidFormat: Connection.GuidFormat); m_columnDefinitionPayloadUsedBytes += payloadLength; } if (!Session.SupportsDeprecateEof) { payload = await Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false); EofPayload.Create(payload.Span); } if (ColumnDefinitions.Length == (Command?.OutParameters?.Count + 1) && ColumnDefinitions[0].Name == SingleCommandPayloadCreator.OutParameterSentinelColumnName) { ContainsCommandParameters = true; } LastInsertId = -1; WarningCount = 0; State = ResultSetState.ReadResultSetHeader; break; } } } catch (Exception ex) { ReadResultSetHeaderException = ex; } finally { BufferState = State; } }
private void WritePreparedStatement(IMySqlCommand command, PreparedStatement preparedStatement, ByteBufferWriter writer) { var parameterCollection = command.RawParameters; if (Log.IsDebugEnabled()) { Log.Debug("Session{0} Preparing command payload; CommandId: {1}; CommandText: {2}", command.Connection.Session.Id, preparedStatement.StatementId, command.CommandText); } writer.Write((byte)CommandKind.StatementExecute); writer.Write(preparedStatement.StatementId); writer.Write((byte)0); writer.Write(1); if (preparedStatement.Parameters?.Length > 0) { // TODO: How to handle incorrect number of parameters? // build subset of parameters for this statement var parameters = new MySqlParameter[preparedStatement.Statement.ParameterNames.Count]; for (var i = 0; i < preparedStatement.Statement.ParameterNames.Count; i++) { var parameterName = preparedStatement.Statement.ParameterNames[i]; var parameterIndex = parameterName is object?(parameterCollection?.NormalizedIndexOf(parameterName) ?? -1) : preparedStatement.Statement.ParameterIndexes[i]; if (parameterIndex == -1 && parameterName is object) { throw new MySqlException("Parameter '{0}' must be defined.".FormatInvariant(parameterName)); } else if (parameterIndex < 0 || parameterIndex >= (parameterCollection?.Count ?? 0)) { throw new MySqlException("Parameter index {0} is invalid when only {1} parameter{2} defined.".FormatInvariant(parameterIndex, parameterCollection?.Count ?? 0, parameterCollection?.Count == 1 ? " is" : "s are")); } parameters[i] = parameterCollection[parameterIndex]; } // write null bitmap byte nullBitmap = 0; for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; if (parameter.Value is null || parameter.Value == DBNull.Value) { nullBitmap |= (byte)(1 << (i % 8)); } if (i % 8 == 7) { writer.Write(nullBitmap); nullBitmap = 0; } } if (parameters.Length % 8 != 0) { writer.Write(nullBitmap); } // write "new parameters bound" flag writer.Write((byte)1); foreach (var parameter in parameters) { // override explicit MySqlDbType with inferred type from the Value var mySqlDbType = parameter.MySqlDbType; var typeMapping = (parameter.Value is null || parameter.Value == DBNull.Value) ? null : TypeMapper.Instance.GetDbTypeMapping(parameter.Value.GetType()); if (typeMapping is object) { var dbType = typeMapping.DbTypes[0]; mySqlDbType = TypeMapper.Instance.GetMySqlDbTypeForDbType(dbType); } writer.Write(TypeMapper.ConvertToColumnTypeAndFlags(mySqlDbType, command.Connection.GuidFormat)); } var options = command.CreateStatementPreparerOptions(); foreach (var parameter in parameters) { parameter.AppendBinary(writer, options); } } }