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 = ExceptionDispatchInfo.Capture(new MySqlException("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 = ExceptionDispatchInfo.Capture(ex); } finally { BufferState = State; } }
internal Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
public ValueTask <int> WriteBytesAsync(ArraySegment <byte> data, IOBehavior ioBehavior) => throw new NotSupportedException();
private ValueTask <int> ScanResultSetAsync(IOBehavior ioBehavior, ResultSet resultSet, CancellationToken cancellationToken) { if (!m_hasMoreResults) { return(default);
internal async Task <bool> NextResultAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { VerifyNotDisposed(); try { do { while (true) { await m_resultSet !.ReadEntireAsync(ioBehavior, cancellationToken).ConfigureAwait(false); await ScanResultSetAsync(ioBehavior, m_resultSet, cancellationToken).ConfigureAwait(false); if (m_hasMoreResults && m_resultSet.ContainsCommandParameters) { await ReadOutParametersAsync(Command !, m_resultSet, ioBehavior, cancellationToken).ConfigureAwait(false); } else { break; } } if (!m_hasMoreResults) { if (m_commandListPosition.CommandIndex < m_commandListPosition.Commands.Count) { Command = m_commandListPosition.Commands[m_commandListPosition.CommandIndex]; using (Command.CancellableCommand.RegisterCancel(cancellationToken)) { var writer = new ByteBufferWriter(); if (!Command.Connection !.Session.IsCancelingQuery && m_payloadCreator.WriteQueryCommand(ref m_commandListPosition, m_cachedProcedures !, writer)) { using var payload = writer.ToPayloadData(); await Command.Connection.Session.SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); await m_resultSet.ReadResultSetHeaderAsync(ioBehavior).ConfigureAwait(false); ActivateResultSet(); m_hasMoreResults = true; } } } } else { ActivateResultSet(); } }while (m_hasMoreResults && (Command !.CommandBehavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0); if (!m_hasMoreResults) { m_resultSet.Reset(); } #if !NETSTANDARD1_3 m_schemaTable = null; #endif return(m_hasMoreResults); } catch (MySqlException) { m_resultSet !.Reset(); m_hasMoreResults = false; #if !NETSTANDARD1_3 m_schemaTable = null; #endif throw; } }
private async ValueTask <MySqlTransaction> BeginTransactionAsync(IsolationLevel isolationLevel, bool?isReadOnly, IOBehavior ioBehavior, CancellationToken cancellationToken) { if (State != ConnectionState.Open) { throw new InvalidOperationException("Connection is not open."); } if (CurrentTransaction is object) { throw new InvalidOperationException("Transactions may not be nested."); } #if !NETSTANDARD1_3 if (m_enlistedTransaction is object) { throw new InvalidOperationException("Cannot begin a transaction when already enlisted in a transaction."); } #endif var isolationLevelValue = isolationLevel switch { IsolationLevel.ReadUncommitted => "read uncommitted", IsolationLevel.ReadCommitted => "read committed", IsolationLevel.RepeatableRead => "repeatable read", IsolationLevel.Serializable => "serializable", IsolationLevel.Snapshot => "repeatable read", // "In terms of the SQL:1992 transaction isolation levels, the default InnoDB level is REPEATABLE READ." - http://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-model.html IsolationLevel.Unspecified => "repeatable read", _ => throw new NotSupportedException("IsolationLevel.{0} is not supported.".FormatInvariant(isolationLevel)) }; using (var cmd = new MySqlCommand($"set session transaction isolation level {isolationLevelValue};", this)) { await cmd.ExecuteNonQueryAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var consistentSnapshotText = isolationLevel == IsolationLevel.Snapshot ? " with consistent snapshot" : ""; var readOnlyText = isReadOnly switch { true => " read only", false => " read write", null => "", }; var separatorText = (consistentSnapshotText.Length == 0 || readOnlyText.Length == 0) ? "" : ","; cmd.CommandText = $"start transaction{consistentSnapshotText}{separatorText}{readOnlyText};"; await cmd.ExecuteNonQueryAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } var transaction = new MySqlTransaction(this, isolationLevel); CurrentTransaction = transaction; return(transaction); }
private ValueTask <Row> ScanRowAsync(IOBehavior ioBehavior, Row row, CancellationToken cancellationToken) { // if we've already read past the end of this resultset, Read returns false if (BufferState == ResultSetState.HasMoreData || BufferState == ResultSetState.NoMoreData || BufferState == ResultSetState.None) { return(new ValueTask <Row>((Row)null)); } using (Command.RegisterCancel(cancellationToken)) { var payloadValueTask = Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None); return(payloadValueTask.IsCompletedSuccessfully ? new ValueTask <Row>(ScanRowAsyncRemainder(payloadValueTask.Result, row)) : new ValueTask <Row>(ScanRowAsyncAwaited(payloadValueTask.AsTask(), row, cancellationToken))); } async Task <Row> ScanRowAsyncAwaited(Task <PayloadData> payloadTask, Row row_, CancellationToken token) { PayloadData payloadData; try { payloadData = await payloadTask.ConfigureAwait(false); } catch (MySqlException ex) { BufferState = State = ResultSetState.NoMoreData; if (ex.Number == (int)MySqlErrorCode.QueryInterrupted) { token.ThrowIfCancellationRequested(); } throw; } return(ScanRowAsyncRemainder(payloadData, row_)); } Row ScanRowAsyncRemainder(PayloadData payload, Row row_) { if (payload.HeaderByte == EofPayload.Signature) { if (Session.SupportsDeprecateEof && OkPayload.IsOk(payload, Session.SupportsDeprecateEof)) { var ok = OkPayload.Create(payload, Session.SupportsDeprecateEof); BufferState = (ok.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData; m_rowBuffered = null; return(null); } if (!Session.SupportsDeprecateEof && EofPayload.IsEof(payload)) { var eof = EofPayload.Create(payload); BufferState = (eof.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData; m_rowBuffered = null; return(null); } } if (row_ == null) { row_ = DataReader.ResultSetProtocol == ResultSetProtocol.Binary ? (Row) new BinaryRow(this) : new TextRow(this); } row_.SetData(payload.ArraySegment); m_rowBuffered = row_; m_hasRows = true; BufferState = ResultSetState.ReadingRows; return(row_); } }
private async Task <bool> OpenSocketAsync(IEnumerable <string> hostnames, int port, IOBehavior ioBehavior, CancellationToken cancellationToken) { foreach (var hostname in hostnames) { IPAddress[] ipAddresses; try { #if NETSTANDARD1_3 // Dns.GetHostAddresses isn't available until netstandard 2.0: https://github.com/dotnet/corefx/pull/11950 ipAddresses = await Dns.GetHostAddressesAsync(hostname).ConfigureAwait(false); #else if (ioBehavior == IOBehavior.Asynchronous) { ipAddresses = await Dns.GetHostAddressesAsync(hostname).ConfigureAwait(false); } else { ipAddresses = Dns.GetHostAddresses(hostname); } #endif } catch (SocketException) { // name couldn't be resolved continue; } // need to try IP Addresses one at a time: https://github.com/dotnet/corefx/issues/5829 foreach (var ipAddress in ipAddresses) { Socket socket = null; try { socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); using (cancellationToken.Register(() => socket.Dispose())) { try { if (ioBehavior == IOBehavior.Asynchronous) { #if NETSTANDARD1_3 await socket.ConnectAsync(ipAddress, port).ConfigureAwait(false); #else await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, ipAddress, port, null).ConfigureAwait(false); #endif } else { #if NETSTANDARD1_3 await socket.ConnectAsync(ipAddress, port).ConfigureAwait(false); #else socket.Connect(ipAddress, port); #endif } } catch (ObjectDisposedException ex) when(cancellationToken.IsCancellationRequested) { throw new MySqlException("Connect Timeout expired.", ex); } } } catch (SocketException) { Utility.Dispose(ref socket); continue; } m_socket = socket; m_transmitter = new PacketTransmitter(m_socket); m_state = State.Connected; return(true); } } return(false); }
private Task TryAsync <TArg>(Func <TArg, IOBehavior, CancellationToken, Task> func, TArg arg, IOBehavior ioBehavior, CancellationToken cancellationToken) { VerifyConnected(); var task = func(arg, ioBehavior, cancellationToken); if (task.Status == TaskStatus.RanToCompletion) { return(task); } return(task.ContinueWith(TryAsyncContinuation, cancellationToken, TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)); }
// Continues a conversation with the server by receiving a response to a packet sent with 'Send' or 'SendReply'. public ValueTask <PayloadData> ReceiveReplyAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) => TryAsync(m_transmitter.ReceiveReplyAsync, ioBehavior, cancellationToken);
// Continues a conversation with the server by sending a reply to a packet received with 'Receive' or 'ReceiveReply'. public Task SendReplyAsync(PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken) => TryAsync(m_transmitter.SendReplyAsync, payload, ioBehavior, cancellationToken);
public async Task ResetConnectionAsync(string userId, string password, string database, IOBehavior ioBehavior, CancellationToken cancellationToken) { if (ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0) { await SendAsync(ResetConnectionPayload.Create(), ioBehavior, cancellationToken).ConfigureAwait(false); var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); // the "reset connection" packet also resets the connection charset, so we need to change that back to our default payload = new PayloadData(new ArraySegment <byte>(Payload.CreateEofStringPayload(CommandKind.Query, "SET NAMES utf8mb4;"))); await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); OkPayload.Create(payload); } else { // optimistically hash the password with the challenge from the initial handshake (supported by MariaDB; doesn't appear to be supported by MySQL) var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, password); var payload = ChangeUserPayload.Create(userId, hashedPassword, database); await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature) { // if the server didn't support the hashed password; rehash with the new challenge var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload); if (switchRequest.Name != "mysql_native_password") { throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(switchRequest.Name)); } hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(switchRequest.Data, 0, password); payload = new PayloadData(new ArraySegment <byte>(hashedPassword)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); } OkPayload.Create(payload); } }
public static async Task <PayloadData> AuthenticateAsync(ConnectionSettings cs, byte[] switchRequestPayloadData, ServerSession session, IOBehavior ioBehavior, CancellationToken cancellationToken) { using var innerStream = new NegotiateToMySqlConverterStream(session, ioBehavior, cancellationToken); using var negotiateStream = new NegotiateStream(innerStream); var targetName = cs.ServerSPN ?? GetServicePrincipalName(switchRequestPayloadData); #if NETSTANDARD1_3 await negotiateStream.AuthenticateAsClientAsync(CredentialCache.DefaultNetworkCredentials, targetName).ConfigureAwait(false); #else if (ioBehavior == IOBehavior.Synchronous) { negotiateStream.AuthenticateAsClient(CredentialCache.DefaultNetworkCredentials, targetName); } else { await negotiateStream.AuthenticateAsClientAsync(CredentialCache.DefaultNetworkCredentials, targetName).ConfigureAwait(false); } #endif if (cs.ServerSPN is object && !negotiateStream.IsMutuallyAuthenticated) { // Negotiate used NTLM fallback, server name cannot be verified. throw new AuthenticationException(String.Format( "GSSAPI : Unable to verify server principal name using authentication type {0}", negotiateStream.RemoteIdentity?.AuthenticationType)); } if (innerStream.MySQLProtocolPayload is PayloadData payload) { // return already pre-read OK packet. return(payload); } // Read final OK packet from server return(await session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false)); }
private async Task <int> LoadAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { if (Connection is null) { throw new InvalidOperationException("Connection not set"); } if (!string.IsNullOrWhiteSpace(FileName) && SourceStream is object) { throw new InvalidOperationException("Cannot set both FileName and SourceStream"); } // LOCAL INFILE case if (!string.IsNullOrWhiteSpace(FileName) && Local) { SourceStream = CreateFileStream(FileName !); FileName = null; } if (string.IsNullOrWhiteSpace(FileName) && SourceStream is object) { if (!Local) { throw new InvalidOperationException("Cannot use SourceStream when Local is not true."); } FileName = GenerateSourceStreamName(); lock (s_lock) s_streams.Add(FileName, SourceStream); } if (string.IsNullOrWhiteSpace(FileName) || string.IsNullOrWhiteSpace(TableName)) { // This is intentionally a different exception to what is thrown by MySql.Data because // it does not handle null or empty FileName and TableName. // The baseline client simply tries to use the given values, resulting in a NullReferenceException for // a null FileName, a MySqlException with an inner FileStream exception for an empty FileName, // and a MySqlException with a syntax error if the TableName is null or empty. throw new InvalidOperationException("FileName or SourceStream, and TableName are required."); } bool closeConnection = false; if (Connection.State != ConnectionState.Open) { closeConnection = true; Connection.Open(); } bool closeStream = SourceStream is object; try { if (Local && !Connection.AllowLoadLocalInfile) { throw new NotSupportedException("To use MySqlBulkLoader.Local=true, set AllowLoadLocalInfile=true in the connection string. See https://fl.vu/mysql-load-data"); } var commandString = BuildSqlCommand(); var cmd = new MySqlCommand(commandString, Connection, Connection.CurrentTransaction) { CommandTimeout = Timeout, }; var result = await cmd.ExecuteNonQueryAsync(ioBehavior, cancellationToken).ConfigureAwait(false); closeStream = false; return(result); } finally { if (closeStream) { using (GetAndRemoveStream(FileName !)) { // close the stream } } if (closeConnection) { Connection.Close(); } } }
private async ValueTask <int> WriteToServerAsync(IDataReader dataReader, IOBehavior ioBehavior, CancellationToken cancellationToken)
private ValueTask <PayloadData> TryAsync(Func <IOBehavior, CancellationToken, ValueTask <PayloadData> > func, IOBehavior ioBehavior, CancellationToken cancellationToken) { VerifyConnected(); var task = func(ioBehavior, cancellationToken); if (task.IsCompletedSuccessfully) { if (task.Result.HeaderByte != ErrorPayload.Signature) { return(task); } var exception = ErrorPayload.Create(task.Result).ToException(); #if NETSTANDARD1_3 return(new ValueTask <PayloadData>(Task.FromException <PayloadData>(exception))); #else var tcs = new TaskCompletionSource <PayloadData>(); tcs.SetException(exception); return(new ValueTask <PayloadData>(tcs.Task)); #endif } return(new ValueTask <PayloadData>(task.AsTask() .ContinueWith(TryAsyncContinuation, cancellationToken, TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default))); }
private async ValueTask WriteToServerAsync(IDataReader dataReader, IOBehavior ioBehavior, CancellationToken cancellationToken) #endif { var tableName = DestinationTableName ?? throw new InvalidOperationException("DestinationTableName must be set before calling WriteToServer"); var bulkLoader = new MySqlBulkLoader(m_connection) { CharacterSet = "utf8mb4", EscapeCharacter = '\\', FieldQuotationCharacter = '\0', FieldTerminator = "\t", LinePrefix = null, LineTerminator = "\n", Local = true, NumberOfLinesToSkip = 0, Source = dataReader ?? throw new ArgumentNullException(nameof(dataReader)), TableName = tableName, Timeout = BulkCopyTimeout, }; var closeConnection = false; if (m_connection.State != ConnectionState.Open) { m_connection.Open(); closeConnection = true; } using (var cmd = new MySqlCommand("select * from " + QuoteIdentifier(tableName) + ";", m_connection, m_transaction)) using (var reader = (MySqlDataReader)await cmd.ExecuteReaderAsync(CommandBehavior.SchemaOnly, ioBehavior, cancellationToken).ConfigureAwait(false)) { var schema = reader.GetColumnSchema(); for (var i = 0; i < schema.Count; i++) { if (schema[i].DataTypeName == "BIT") { bulkLoader.Columns.Add($"@col{i}"); bulkLoader.Expressions.Add($"`{reader.GetName(i)}` = CAST(@col{i} AS UNSIGNED)"); } else if (schema[i].DataTypeName == "YEAR") { // the current code can't distinguish between 0 = 0000 and 0 = 2000 throw new NotSupportedException("'YEAR' columns are not supported by MySqlBulkLoader."); } else { var type = schema[i].DataType; if (type == typeof(byte[]) || (type == typeof(Guid) && (m_connection.GuidFormat == MySqlGuidFormat.Binary16 || m_connection.GuidFormat == MySqlGuidFormat.LittleEndianBinary16 || m_connection.GuidFormat == MySqlGuidFormat.TimeSwapBinary16))) { bulkLoader.Columns.Add($"@col{i}"); bulkLoader.Expressions.Add($"`{reader.GetName(i)}` = UNHEX(@col{i})"); } else { bulkLoader.Columns.Add(QuoteIdentifier(reader.GetName(i))); } } } } await bulkLoader.LoadAsync(ioBehavior, cancellationToken).ConfigureAwait(false); if (closeConnection) { m_connection.Close(); } #if !NETSTANDARD2_1 && !NETCOREAPP3_0 return(default);
public async Task ConnectAsync(IEnumerable <string> hosts, int port, string userId, string password, string database, IOBehavior ioBehavior, CancellationToken cancellationToken) { var connected = await OpenSocketAsync(hosts, port, ioBehavior, cancellationToken).ConfigureAwait(false); if (!connected) { throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } var payload = await ReceiveAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); if (initialHandshake.AuthPluginName != "mysql_native_password") { throw new NotSupportedException("Authentication method '{0}' is not supported.".FormatInvariant(initialHandshake.AuthPluginName)); } ServerVersion = new ServerVersion(Encoding.ASCII.GetString(initialHandshake.ServerVersion)); ConnectionId = initialHandshake.ConnectionId; AuthPluginData = initialHandshake.AuthPluginData; var response = HandshakeResponse41Packet.Create(initialHandshake, userId, password, database); payload = new PayloadData(new ArraySegment <byte>(response)); await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false); await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); }
internal Task CloseAsync(IOBehavior ioBehavior) => CloseAsync(changeState: true, ioBehavior);
public async Task <ResultSet> ReadResultSetHeaderAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { // ResultSet can be re-used, so initialize everything BufferState = ResultSetState.None; ColumnDefinitions = null; LastInsertId = 0; RecordsAffected = 0; State = ResultSetState.None; m_dataLengths = null; m_dataOffsets = null; m_readBuffer.Clear(); m_row = null; m_rowBuffered = null; while (true) { var payload = await Session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var firstByte = payload.HeaderByte; if (firstByte == OkPayload.Signature) { var ok = OkPayload.Create(payload); RecordsAffected = ok.AffectedRowCount; LastInsertId = ok.LastInsertId; ColumnDefinitions = null; State = (ok.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData; if (State == ResultSetState.NoMoreData) { break; } } else if (firstByte == 0xFB) { throw new NotSupportedException("Don't support LOCAL_INFILE_Request"); } else { var reader = new ByteArrayReader(payload.ArraySegment); var columnCount = (int)reader.ReadLengthEncodedInteger(); ColumnDefinitions = new ColumnDefinitionPayload[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).ConfigureAwait(false); ColumnDefinitions[column] = ColumnDefinitionPayload.Create(payload); } payload = await Session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); EofPayload.Create(payload); LastInsertId = -1; State = ResultSetState.ReadResultSetHeader; break; } } BufferState = State; return(this); }
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_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.Instance, 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) 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 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.Count) { Utility.Resize(ref m_columnDefinitionPayloads, m_columnDefinitionPayloadUsedBytes + arraySegment.Count); } Buffer.BlockCopy(arraySegment.Array, arraySegment.Offset, m_columnDefinitionPayloads.Array, m_columnDefinitionPayloadUsedBytes, arraySegment.Count); var columnDefinition = ColumnDefinitionPayload.Create(new ResizableArraySegment <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); }
public static async Task <CachedProcedure?> FillAsync(IOBehavior ioBehavior, MySqlConnection connection, string schema, string component, CancellationToken cancellationToken) { // try to use mysql.proc first, as it is much faster if (connection.Session.ServerVersion.Version < ServerVersions.RemovesMySqlProcTable && !connection.Session.ProcAccessDenied) { try { using var cmd = connection.CreateCommand(); cmd.Transaction = connection.CurrentTransaction; cmd.CommandText = @"SELECT param_list, returns FROM mysql.proc WHERE db = @schema AND name = @component"; cmd.Parameters.AddWithValue("@schema", schema); cmd.Parameters.AddWithValue("@component", component); using var reader = await cmd.ExecuteReaderNoResetTimeoutAsync(CommandBehavior.Default, ioBehavior, cancellationToken).ConfigureAwait(false); var exists = await reader.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false); if (!exists) { return(null); } var parametersSqlBytes = (byte[])reader.GetValue(0); var returnsSqlBytes = (byte[])reader.GetValue(1); // ASSUME this is UTF-8 encoded; it's possible that the `character_set_client` column would need to be used? var parametersSql = Encoding.UTF8.GetString(parametersSqlBytes); var returnsSql = Encoding.UTF8.GetString(returnsSqlBytes); var parsedParameters = ParseParameters(parametersSql); if (returnsSql.Length != 0) { var returnDataType = ParseDataType(returnsSql, out var unsigned, out var length); parsedParameters.Insert(0, CreateCachedParameter(0, null, "", returnDataType, unsigned, length, returnsSql)); } return(new CachedProcedure(schema, component, parsedParameters)); } catch (MySqlException ex) { Log.Warn("Session{0} failed to retrieve metadata for Schema={1} Component={2}; falling back to INFORMATION_SCHEMA. Error: {3}", connection.Session.Id, schema, component, ex.Message); if (ex.ErrorCode == MySqlErrorCode.TableAccessDenied) { connection.Session.ProcAccessDenied = true; } } } if (connection.Session.ServerVersion.Version < ServerVersions.SupportsProcedureCache) { Log.Warn("Session{0} ServerVersion={1} does not support cached procedures", connection.Session.Id, connection.Session.ServerVersion.OriginalString); return(null); } var parameters = new List <CachedParameter>(); int routineCount; using (var cmd = connection.CreateCommand()) { cmd.Transaction = connection.CurrentTransaction; cmd.CommandText = @"SELECT COUNT(*) FROM information_schema.routines WHERE ROUTINE_SCHEMA = @schema AND ROUTINE_NAME = @component; SELECT ORDINAL_POSITION, PARAMETER_MODE, PARAMETER_NAME, DTD_IDENTIFIER FROM information_schema.parameters WHERE SPECIFIC_SCHEMA = @schema AND SPECIFIC_NAME = @component ORDER BY ORDINAL_POSITION" ; cmd.Parameters.AddWithValue("@schema", schema); cmd.Parameters.AddWithValue("@component", component); using var reader = await cmd.ExecuteReaderNoResetTimeoutAsync(CommandBehavior.Default, ioBehavior, cancellationToken).ConfigureAwait(false); await reader.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false); routineCount = reader.GetInt32(0); await reader.NextResultAsync(ioBehavior, cancellationToken).ConfigureAwait(false); while (await reader.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false)) { var dataType = ParseDataType(reader.GetString(3), out var unsigned, out var length); parameters.Add(new( reader.GetInt32(0), !reader.IsDBNull(1) ? reader.GetString(1) : null, !reader.IsDBNull(2) ? reader.GetString(2) : "", dataType, unsigned, length )); } } if (Log.IsInfoEnabled()) { Log.Info("Procedure for Schema={0} Component={1} has RoutineCount={2}, ParameterCount={3}", schema, component, routineCount, parameters.Count); } return(routineCount == 0 ? null : new CachedProcedure(schema, component, parameters)); }
internal Task <bool> ReadAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) => m_resultSet !.ReadAsync(ioBehavior, cancellationToken);
private async Task DoPrepareAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { var statementPreparer = new StatementPreparer(CommandText, m_parameterCollection, CreateStatementPreparerOptions()); var parsedStatements = statementPreparer.SplitStatements(); if (parsedStatements.Statements.Count > 1) { throw new NotSupportedException("Multiple semicolon-delimited SQL statements are not supported by MySqlCommand.Prepare"); } var columnsAndParameters = new ResizableArray <byte>(); var columnsAndParametersSize = 0; var preparedStatements = new List <PreparedStatement>(parsedStatements.Statements.Count); foreach (var statement in parsedStatements.Statements) { await Connection.Session.SendAsync(new PayloadData(statement.StatementBytes), ioBehavior, cancellationToken).ConfigureAwait(false); var payload = await Connection.Session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); var response = StatementPrepareResponsePayload.Create(payload.AsSpan()); ColumnDefinitionPayload[] parameters = null; if (response.ParameterCount > 0) { parameters = new ColumnDefinitionPayload[response.ParameterCount]; for (var i = 0; i < response.ParameterCount; i++) { payload = await Connection.Session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); Utility.Resize(ref columnsAndParameters, columnsAndParametersSize + payload.ArraySegment.Count); Buffer.BlockCopy(payload.ArraySegment.Array, payload.ArraySegment.Offset, columnsAndParameters.Array, columnsAndParametersSize, payload.ArraySegment.Count); parameters[i] = ColumnDefinitionPayload.Create(new ResizableArraySegment <byte>(columnsAndParameters, columnsAndParametersSize, payload.ArraySegment.Count)); columnsAndParametersSize += payload.ArraySegment.Count; } if (!Connection.Session.SupportsDeprecateEof) { payload = await Connection.Session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); EofPayload.Create(payload.AsSpan()); } } ColumnDefinitionPayload[] columns = null; if (response.ColumnCount > 0) { columns = new ColumnDefinitionPayload[response.ColumnCount]; for (var i = 0; i < response.ColumnCount; i++) { payload = await Connection.Session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); Utility.Resize(ref columnsAndParameters, columnsAndParametersSize + payload.ArraySegment.Count); Buffer.BlockCopy(payload.ArraySegment.Array, payload.ArraySegment.Offset, columnsAndParameters.Array, columnsAndParametersSize, payload.ArraySegment.Count); columns[i] = ColumnDefinitionPayload.Create(new ResizableArraySegment <byte>(columnsAndParameters, columnsAndParametersSize, payload.ArraySegment.Count)); columnsAndParametersSize += payload.ArraySegment.Count; } if (!Connection.Session.SupportsDeprecateEof) { payload = await Connection.Session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false); EofPayload.Create(payload.AsSpan()); } } preparedStatements.Add(new PreparedStatement(response.StatementId, statement, columns, parameters)); } Connection.Session.AddPreparedStatement(CommandText, new PreparedStatements(preparedStatements, parsedStatements)); }
private async Task ExecuteSavepointAsync(string command, string savepointName, IOBehavior ioBehavior, CancellationToken cancellationToken) { VerifyValid(); if (savepointName is null) { throw new ArgumentNullException(nameof(savepointName)); } if (savepointName.Length == 0) { throw new ArgumentException("savepointName must not be empty", nameof(savepointName)); } using var cmd = new MySqlCommand(command + "savepoint " + QuoteIdentifier(savepointName), Connection, this); await cmd.ExecuteNonQueryAsync(ioBehavior, cancellationToken).ConfigureAwait(false); }
public virtual async Task <DbDataReader> ExecuteReaderAsync(string commandText, MySqlParameterCollection parameterCollection, CommandBehavior behavior, IOBehavior ioBehavior, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (Log.IsDebugEnabled()) { Log.Debug("Session{0} ExecuteBehavior {1} CommandText: {2}", m_command.Connection.Session.Id, ioBehavior, commandText); } using (var payload = CreateQueryPayload(commandText, parameterCollection)) using (m_command.RegisterCancel(cancellationToken)) { m_command.Connection.Session.StartQuerying(m_command); m_command.LastInsertedId = -1; try { await m_command.Connection.Session.SendAsync(payload, ioBehavior, CancellationToken.None).ConfigureAwait(false); return(await MySqlDataReader.CreateAsync(m_command, behavior, ResultSetProtocol.Text, ioBehavior).ConfigureAwait(false)); } catch (MySqlException ex) when(ex.Number == (int)MySqlErrorCode.QueryInterrupted && cancellationToken.IsCancellationRequested) { Log.Warn("Session{0} query was interrupted", m_command.Connection.Session.Id); throw new OperationCanceledException(cancellationToken); } catch (Exception ex) when(payload.ArraySegment.Count > 4_194_304 && (ex is SocketException || ex is IOException || ex is MySqlProtocolException)) { // the default MySQL Server value for max_allowed_packet (in MySQL 5.7) is 4MiB: https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_allowed_packet // use "decimal megabytes" (to round up) when creating the exception message int megabytes = payload.ArraySegment.Count / 1_000_000; throw new MySqlException("Error submitting {0}MB packet; ensure 'max_allowed_packet' is greater than {0}MB.".FormatInvariant(megabytes), ex); } } }
public ValueTask <int> ReadBytesAsync(ArraySegment <byte> buffer, IOBehavior ioBehavior) => m_compressedPayloadHandler.ReadBytesAsync(buffer, m_protocolErrorBehavior, ioBehavior);
private async ValueTask <int> WriteToServerAsync(DataTable dataTable, IOBehavior ioBehavior, CancellationToken cancellationToken)
private ValueTask <Row> ScanRowAsync(IOBehavior ioBehavior, Row row, CancellationToken cancellationToken) { // if we've already read past the end of this resultset, Read returns false if (BufferState == ResultSetState.HasMoreData || BufferState == ResultSetState.NoMoreData || BufferState == ResultSetState.None) { return(new ValueTask <Row>((Row)null)); } using (Command.CancellableCommand.RegisterCancel(cancellationToken)) { var payloadValueTask = Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None); return(payloadValueTask.IsCompletedSuccessfully ? new ValueTask <Row>(ScanRowAsyncRemainder(payloadValueTask.Result, row)) : new ValueTask <Row>(ScanRowAsyncAwaited(payloadValueTask.AsTask(), row, cancellationToken))); } async Task <Row> ScanRowAsyncAwaited(Task <PayloadData> payloadTask, Row row_, CancellationToken token) { PayloadData payloadData; try { payloadData = await payloadTask.ConfigureAwait(false); } catch (MySqlException ex) { BufferState = State = ResultSetState.NoMoreData; if (ex.Number == (int)MySqlErrorCode.QueryInterrupted) { token.ThrowIfCancellationRequested(); } throw; } return(ScanRowAsyncRemainder(payloadData, row_)); } Row ScanRowAsyncRemainder(PayloadData payload, Row row_) { if (payload.HeaderByte == EofPayload.Signature) { var span = payload.AsSpan(); if (Session.SupportsDeprecateEof && OkPayload.IsOk(span, Session.SupportsDeprecateEof)) { var ok = OkPayload.Create(span, Session.SupportsDeprecateEof, Session.SupportsSessionTrack); BufferState = (ok.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData; m_rowBuffered = null; return(null); } if (!Session.SupportsDeprecateEof && EofPayload.IsEof(payload)) { var eof = EofPayload.Create(span); BufferState = (eof.ServerStatus & ServerStatus.MoreResultsExist) == 0 ? ResultSetState.NoMoreData : ResultSetState.HasMoreData; m_rowBuffered = null; return(null); } } if (row_ is null) { bool isBinaryRow = false; if (payload.HeaderByte == 0 && !Connection.IgnorePrepare) { // this might be a binary row, but it might also be a text row whose first column is zero bytes long; try reading // the row as a series of length-encoded values (the text format) to see if this might plausibly be a text row var isTextRow = false; var reader = new ByteArrayReader(payload.AsSpan()); var columnCount = 0; while (reader.BytesRemaining > 0) { int length; var firstByte = reader.ReadByte(); if (firstByte == 0xFB) { // NULL length = 0; } else if (firstByte == 0xFC) { // two-byte length-encoded integer if (reader.BytesRemaining < 2) { break; } length = unchecked ((int)reader.ReadFixedLengthUInt32(2)); } else if (firstByte == 0xFD) { // three-byte length-encoded integer if (reader.BytesRemaining < 3) { break; } length = unchecked ((int)reader.ReadFixedLengthUInt32(3)); } else if (firstByte == 0xFE) { // eight-byte length-encoded integer if (reader.BytesRemaining < 8) { break; } length = checked ((int)reader.ReadFixedLengthUInt64(8)); } else if (firstByte == 0xFF) { // invalid length prefix break; } else { // single-byte length length = firstByte; } if (reader.BytesRemaining < length) { break; } reader.Offset += length; columnCount++; if (columnCount == ColumnDefinitions.Length) { // if we used up all the bytes reading exactly 'ColumnDefinitions' length-encoded columns, then assume this is a text row if (reader.BytesRemaining == 0) { isTextRow = true; } break; } } isBinaryRow = !isTextRow; } row_ = isBinaryRow ? (Row) new BinaryRow(this) : new TextRow(this); } row_.SetData(payload.ArraySegment); m_rowBuffered = row_; m_hasRows = true; BufferState = ResultSetState.ReadingRows; return(row_); } }
public async Task ReadEntireAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) { while (State is ResultSetState.ReadingRows or ResultSetState.ReadResultSetHeader) await ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false); }