diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index da5a2d1062..2f5debba0a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -171,6 +171,9 @@ Microsoft\Data\SqlClient\RowsCopiedEventHandler.cs + + + Microsoft\Data\SqlClient\SqlSequentialTextReader.cs Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs @@ -633,7 +636,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 2580fab991..c1884a03a8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -515,6 +515,9 @@ Microsoft\Data\SqlClient\SqlStatistics.cs + + Microsoft\Data\SqlClient\SqlSequentialTextReader.cs + Microsoft\Data\SqlClient\SqlSymmetricKeyCache.cs @@ -616,7 +619,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs deleted file mode 100644 index 504fd99934..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs +++ /dev/null @@ -1,542 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - sealed internal class SqlSequentialTextReader : System.IO.TextReader - { - private SqlDataReader _reader; // The SqlDataReader that we are reading data from - private int _columnIndex; // The index of out column in the table - private Encoding _encoding; // Encoding for this character stream - private Decoder _decoder; // Decoder based on the encoding (NOTE: Decoders are stateful as they are designed to process streams of data) - private byte[] _leftOverBytes; // Bytes leftover from the last Read() operation - this can be null if there were no bytes leftover (Possible optimization: re-use the same array?) - private int _peekedChar; // The last character that we peeked at (or -1 if we haven't peeked at anything) - private Task _currentTask; // The current async task - private CancellationTokenSource _disposalTokenSource; // Used to indicate that a cancellation is requested due to disposal - - internal SqlSequentialTextReader(SqlDataReader reader, int columnIndex, Encoding encoding) - { - Debug.Assert(reader != null, "Null reader when creating sequential textreader"); - Debug.Assert(columnIndex >= 0, "Invalid column index when creating sequential textreader"); - Debug.Assert(encoding != null, "Null encoding when creating sequential textreader"); - - _reader = reader; - _columnIndex = columnIndex; - _encoding = encoding; - _decoder = encoding.GetDecoder(); - _leftOverBytes = null; - _peekedChar = -1; - _currentTask = null; - _disposalTokenSource = new CancellationTokenSource(); - } - - internal int ColumnIndex - { - get { return _columnIndex; } - } - - public override int Peek() - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - if (IsClosed) - { - throw ADP.ObjectDisposed(this); - } - - if (!HasPeekedChar) - { - _peekedChar = Read(); - } - - Debug.Assert(_peekedChar == -1 || ((_peekedChar >= char.MinValue) && (_peekedChar <= char.MaxValue)), $"Bad peeked character: {_peekedChar}"); - return _peekedChar; - } - - public override int Read() - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - if (IsClosed) - { - throw ADP.ObjectDisposed(this); - } - - int readChar = -1; - - // If there is already a peeked char, then return it - if (HasPeekedChar) - { - readChar = _peekedChar; - _peekedChar = -1; - } - // If there is data available try to read a char - else - { - char[] tempBuffer = new char[1]; - int charsRead = InternalRead(tempBuffer, 0, 1); - if (charsRead == 1) - { - readChar = tempBuffer[0]; - } - } - - Debug.Assert(readChar == -1 || ((readChar >= char.MinValue) && (readChar <= char.MaxValue)), $"Bad read character: {readChar}"); - return readChar; - } - - public override int Read(char[] buffer, int index, int count) - { - ValidateReadParameters(buffer, index, count); - - if (IsClosed) - { - throw ADP.ObjectDisposed(this); - } - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - int charsRead = 0; - int charsNeeded = count; - // Load in peeked char - if ((charsNeeded > 0) && (HasPeekedChar)) - { - Debug.Assert((_peekedChar >= char.MinValue) && (_peekedChar <= char.MaxValue), $"Bad peeked character: {_peekedChar}"); - buffer[index + charsRead] = (char)_peekedChar; - charsRead++; - charsNeeded--; - _peekedChar = -1; - } - - // If we need more data and there is data avaiable, read - charsRead += InternalRead(buffer, index + charsRead, charsNeeded); - - return charsRead; - } - - public override Task ReadAsync(char[] buffer, int index, int count) - { - ValidateReadParameters(buffer, index, count); - TaskCompletionSource completion = new TaskCompletionSource(); - - if (IsClosed) - { - completion.SetException(ADP.ExceptionWithStackTrace(ADP.ObjectDisposed(this))); - } - else - { - try - { - Task original = Interlocked.CompareExchange(ref _currentTask, completion.Task, null); - if (original != null) - { - completion.SetException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - } - else - { - bool completedSynchronously = true; - int charsRead = 0; - int adjustedIndex = index; - int charsNeeded = count; - - // Load in peeked char - if ((HasPeekedChar) && (charsNeeded > 0)) - { - // Take a copy of _peekedChar in case it is cleared during close - int peekedChar = _peekedChar; - if (peekedChar >= char.MinValue) - { - Debug.Assert((_peekedChar >= char.MinValue) && (_peekedChar <= char.MaxValue), $"Bad peeked character: {_peekedChar}"); - buffer[adjustedIndex] = (char)peekedChar; - adjustedIndex++; - charsRead++; - charsNeeded--; - _peekedChar = -1; - } - } - - int byteBufferUsed; - byte[] byteBuffer = PrepareByteBuffer(charsNeeded, out byteBufferUsed); - - // Permit a 0 byte read in order to advance the reader to the correct column - if ((byteBufferUsed < byteBuffer.Length) || (byteBuffer.Length == 0)) - { - int bytesRead; - var reader = _reader; - if (reader != null) - { - Task getBytesTask = reader.GetBytesAsync(_columnIndex, byteBuffer, byteBufferUsed, byteBuffer.Length - byteBufferUsed, Timeout.Infinite, _disposalTokenSource.Token, out bytesRead); - if (getBytesTask == null) - { - byteBufferUsed += bytesRead; - } - else - { - // We need more data - setup the callback, and mark this as not completed sync - completedSynchronously = false; - getBytesTask.ContinueWith((t) => - { - _currentTask = null; - // If we completed but the textreader is closed, then report cancellation - if ((t.Status == TaskStatus.RanToCompletion) && (!IsClosed)) - { - try - { - int bytesReadFromStream = t.Result; - byteBufferUsed += bytesReadFromStream; - if (byteBufferUsed > 0) - { - charsRead += DecodeBytesToChars(byteBuffer, byteBufferUsed, buffer, adjustedIndex, charsNeeded); - } - completion.SetResult(charsRead); - } - catch (Exception ex) - { - completion.SetException(ex); - } - } - else if (IsClosed) - { - completion.SetException(ADP.ExceptionWithStackTrace(ADP.ObjectDisposed(this))); - } - else if (t.Status == TaskStatus.Faulted) - { - if (t.Exception.InnerException is SqlException) - { - // ReadAsync can't throw a SqlException, so wrap it in an IOException - completion.SetException(ADP.ExceptionWithStackTrace(ADP.ErrorReadingFromStream(t.Exception.InnerException))); - } - else - { - completion.SetException(t.Exception.InnerException); - } - } - else - { - completion.SetCanceled(); - } - }, TaskScheduler.Default); - } - - - if ((completedSynchronously) && (byteBufferUsed > 0)) - { - // No more data needed, decode what we have - charsRead += DecodeBytesToChars(byteBuffer, byteBufferUsed, buffer, adjustedIndex, charsNeeded); - } - } - else - { - // Reader is null, close must of happened in the middle of this read - completion.SetException(ADP.ExceptionWithStackTrace(ADP.ObjectDisposed(this))); - } - } - - - if (completedSynchronously) - { - _currentTask = null; - if (IsClosed) - { - completion.SetCanceled(); - } - else - { - completion.SetResult(charsRead); - } - } - } - } - catch (Exception ex) - { - // In case of any errors, ensure that the completion is completed and the task is set back to null if we switched it - completion.TrySetException(ex); - Interlocked.CompareExchange(ref _currentTask, null, completion.Task); - throw; - } - } - - return completion.Task; - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - // Set the textreader as closed - SetClosed(); - } - - base.Dispose(disposing); - } - - /// - /// Forces the TextReader to act as if it was closed - /// This does not actually close the stream, read off the rest of the data or dispose this - /// - internal void SetClosed() - { - _disposalTokenSource.Cancel(); - _reader = null; - _peekedChar = -1; - - // Wait for pending task - var currentTask = _currentTask; - if (currentTask != null) - { - ((IAsyncResult)currentTask).AsyncWaitHandle.WaitOne(); - } - } - - /// - /// Performs the actual reading and converting - /// NOTE: This assumes that buffer, index and count are all valid, we're not closed (!IsClosed) and that there is data left (IsDataLeft()) - /// - /// - /// - /// - /// - private int InternalRead(char[] buffer, int index, int count) - { - Debug.Assert(buffer != null, "Null output buffer"); - Debug.Assert((index >= 0) && (count >= 0) && (index + count <= buffer.Length), $"Bad count: {count} or index: {index}"); - Debug.Assert(!IsClosed, "Can't read while textreader is closed"); - - try - { - int byteBufferUsed; - byte[] byteBuffer = PrepareByteBuffer(count, out byteBufferUsed); - byteBufferUsed += _reader.GetBytesInternalSequential(_columnIndex, byteBuffer, byteBufferUsed, byteBuffer.Length - byteBufferUsed); - - if (byteBufferUsed > 0) - { - return DecodeBytesToChars(byteBuffer, byteBufferUsed, buffer, index, count); - } - else - { - // Nothing to read, or nothing read - return 0; - } - } - catch (SqlException ex) - { - // Read can't throw a SqlException - so wrap it in an IOException - throw ADP.ErrorReadingFromStream(ex); - } - } - - /// - /// Creates a byte array large enough to store all bytes for the characters in the current encoding, then fills it with any leftover bytes - /// - /// Number of characters that are to be read - /// Number of bytes pre-filled by the leftover bytes - /// A byte array of the correct size, pre-filled with leftover bytes - private byte[] PrepareByteBuffer(int numberOfChars, out int byteBufferUsed) - { - Debug.Assert(numberOfChars >= 0, "Can't prepare a byte buffer for negative characters"); - - byte[] byteBuffer; - - if (numberOfChars == 0) - { - byteBuffer = new byte[0]; - byteBufferUsed = 0; - } - else - { - int byteBufferSize = _encoding.GetMaxByteCount(numberOfChars); - - if (_leftOverBytes != null) - { - // If we have more leftover bytes than we need for this conversion, then just re-use the leftover buffer - if (_leftOverBytes.Length > byteBufferSize) - { - byteBuffer = _leftOverBytes; - byteBufferUsed = byteBuffer.Length; - } - else - { - // Otherwise, copy over the leftover buffer - byteBuffer = new byte[byteBufferSize]; - Buffer.BlockCopy(_leftOverBytes, 0, byteBuffer, 0,_leftOverBytes.Length); - byteBufferUsed = _leftOverBytes.Length; - } - } - else - { - byteBuffer = new byte[byteBufferSize]; - byteBufferUsed = 0; - } - } - - return byteBuffer; - } - - /// - /// Decodes the given bytes into characters, and stores the leftover bytes for later use - /// - /// Buffer of bytes to decode - /// Number of bytes to decode from the inBuffer - /// Buffer to write the characters to - /// Offset to start writing to outBuffer at - /// Maximum number of characters to decode - /// The actual number of characters decoded - private int DecodeBytesToChars(byte[] inBuffer, int inBufferCount, char[] outBuffer, int outBufferOffset, int outBufferCount) - { - Debug.Assert(inBuffer != null, "Null input buffer"); - Debug.Assert((inBufferCount > 0) && (inBufferCount <= inBuffer.Length), $"Bad inBufferCount: {inBufferCount}"); - Debug.Assert(outBuffer != null, "Null output buffer"); - Debug.Assert((outBufferOffset >= 0) && (outBufferCount > 0) && (outBufferOffset + outBufferCount <= outBuffer.Length), $"Bad outBufferCount: {outBufferCount} or outBufferOffset: {outBufferOffset}"); - - int charsRead; - int bytesUsed; - bool completed; - _decoder.Convert(inBuffer, 0, inBufferCount, outBuffer, outBufferOffset, outBufferCount, false, out bytesUsed, out charsRead, out completed); - - // completed may be false and there is no spare bytes if the Decoder has stored bytes to use later - if ((!completed) && (bytesUsed < inBufferCount)) - { - _leftOverBytes = new byte[inBufferCount - bytesUsed]; - Buffer.BlockCopy(inBuffer, bytesUsed, _leftOverBytes, 0, _leftOverBytes.Length); - } - else - { - // If Convert() sets completed to true, then it must have used all of the bytes we gave it - Debug.Assert(bytesUsed >= inBufferCount, "Converted completed, but not all bytes were used"); - _leftOverBytes = null; - } - - Debug.Assert(((_reader == null) || (_reader.ColumnDataBytesRemaining() > 0) || (!completed) || (_leftOverBytes == null)), "Stream has run out of data and the decoder finished, but there are leftover bytes"); - Debug.Assert(charsRead > 0, "Converted no chars. Bad encoding?"); - - return charsRead; - } - - /// - /// True if this TextReader is supposed to be closed - /// - private bool IsClosed - { - get { return (_reader == null); } - } - - /// - /// True if there is data left to read - /// - /// - private bool IsDataLeft - { - get { return ((_leftOverBytes != null) || (_reader.ColumnDataBytesRemaining() > 0)); } - } - - /// - /// True if there is a peeked character available - /// - private bool HasPeekedChar - { - get { return (_peekedChar >= char.MinValue); } - } - - /// - /// Checks the the parameters passed into a Read() method are valid - /// - /// - /// - /// - internal static void ValidateReadParameters(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw ADP.ArgumentNull(nameof(buffer)); - } - if (index < 0) - { - throw ADP.ArgumentOutOfRange(nameof(index)); - } - if (count < 0) - { - throw ADP.ArgumentOutOfRange(nameof(count)); - } - try - { - if (checked(index + count) > buffer.Length) - { - throw ExceptionBuilder.InvalidOffsetLength(); - } - } - catch (OverflowException) - { - // If we've overflowed when adding index and count, then they never would have fit into buffer anyway - throw ExceptionBuilder.InvalidOffsetLength(); - } - } - } - - sealed internal class SqlUnicodeEncoding : UnicodeEncoding - { - private static SqlUnicodeEncoding _singletonEncoding = new SqlUnicodeEncoding(); - - private SqlUnicodeEncoding() : base(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: false) - { } - - public override Decoder GetDecoder() - { - return new SqlUnicodeDecoder(); - } - - public override int GetMaxByteCount(int charCount) - { - // SQL Server never sends a BOM, so we can assume that its 2 bytes per char - return charCount * 2; - } - - public static Encoding SqlUnicodeEncodingInstance - { - get { return _singletonEncoding; } - } - - sealed private class SqlUnicodeDecoder : Decoder - { - public override int GetCharCount(byte[] bytes, int index, int count) - { - // SQL Server never sends a BOM, so we can assume that its 2 bytes per char - return count / 2; - } - - public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) - { - // This method is required - simply call Convert() - int bytesUsed; - int charsUsed; - bool completed; - Convert(bytes, byteIndex, byteCount, chars, charIndex, chars.Length - charIndex, true, out bytesUsed, out charsUsed, out completed); - return charsUsed; - } - - public override void Convert(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex, int charCount, bool flush, out int bytesUsed, out int charsUsed, out bool completed) - { - // Assume 2 bytes per char and no BOM - charsUsed = Math.Min(charCount, byteCount / 2); - bytesUsed = charsUsed * 2; - completed = (bytesUsed == byteCount); - - // BlockCopy uses offsets\length measured in bytes, not the actual array index - Buffer.BlockCopy(bytes, byteIndex, chars, charIndex * 2, bytesUsed); - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs similarity index 94% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs index d6ac49f927..436ad67c8e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs @@ -14,13 +14,13 @@ namespace Microsoft.Data.SqlClient sealed internal class SqlSequentialTextReader : System.IO.TextReader { private SqlDataReader _reader; // The SqlDataReader that we are reading data from - private int _columnIndex; // The index of out column in the table - private Encoding _encoding; // Encoding for this character stream - private Decoder _decoder; // Decoder based on the encoding (NOTE: Decoders are stateful as they are designed to process streams of data) + private readonly int _columnIndex; // The index of out column in the table + private readonly Encoding _encoding; // Encoding for this character stream + private readonly Decoder _decoder; // Decoder based on the encoding (NOTE: Decoders are stateful as they are designed to process streams of data) private byte[] _leftOverBytes; // Bytes leftover from the last Read() operation - this can be null if there were no bytes leftover (Possible optimization: re-use the same array?) private int _peekedChar; // The last character that we peeked at (or -1 if we haven't peeked at anything) private Task _currentTask; // The current async task - private CancellationTokenSource _disposalTokenSource; // Used to indicate that a cancellation is requested due to disposal + private readonly CancellationTokenSource _disposalTokenSource; // Used to indicate that a cancellation is requested due to disposal internal SqlSequentialTextReader(SqlDataReader reader, int columnIndex, Encoding encoding) { @@ -38,10 +38,7 @@ internal SqlSequentialTextReader(SqlDataReader reader, int columnIndex, Encoding _disposalTokenSource = new CancellationTokenSource(); } - internal int ColumnIndex - { - get { return _columnIndex; } - } + internal int ColumnIndex => _columnIndex; public override int Peek() { @@ -131,7 +128,7 @@ public override int Read(char[] buffer, int index, int count) public override Task ReadAsync(char[] buffer, int index, int count) { ValidateReadParameters(buffer, index, count); - TaskCompletionSource completion = new TaskCompletionSource(); + TaskCompletionSource completion = new(); if (IsClosed) { @@ -169,17 +166,15 @@ public override Task ReadAsync(char[] buffer, int index, int count) } } - int byteBufferUsed; - byte[] byteBuffer = PrepareByteBuffer(charsNeeded, out byteBufferUsed); + byte[] byteBuffer = PrepareByteBuffer(charsNeeded, out int byteBufferUsed); // Permit a 0 byte read in order to advance the reader to the correct column if ((byteBufferUsed < byteBuffer.Length) || (byteBuffer.Length == 0)) { - int bytesRead; - var reader = _reader; + SqlDataReader reader = _reader; if (reader != null) { - Task getBytesTask = reader.GetBytesAsync(_columnIndex, byteBuffer, byteBufferUsed, byteBuffer.Length - byteBufferUsed, Timeout.Infinite, _disposalTokenSource.Token, out bytesRead); + Task getBytesTask = reader.GetBytesAsync(_columnIndex, byteBuffer, byteBufferUsed, byteBuffer.Length - byteBufferUsed, Timeout.Infinite, _disposalTokenSource.Token, out int bytesRead); if (getBytesTask == null) { byteBufferUsed += bytesRead; @@ -295,7 +290,7 @@ internal void SetClosed() _peekedChar = -1; // Wait for pending task - var currentTask = _currentTask; + Task currentTask = _currentTask; if (currentTask != null) { ((IAsyncResult)currentTask).AsyncWaitHandle.WaitOne(); @@ -318,8 +313,7 @@ private int InternalRead(char[] buffer, int index, int count) try { - int byteBufferUsed; - byte[] byteBuffer = PrepareByteBuffer(count, out byteBufferUsed); + byte[] byteBuffer = PrepareByteBuffer(count, out int byteBufferUsed); byteBufferUsed += _reader.GetBytesInternalSequential(_columnIndex, byteBuffer, byteBufferUsed, byteBuffer.Length - byteBufferUsed); if (byteBufferUsed > 0) @@ -402,10 +396,7 @@ private int DecodeBytesToChars(byte[] inBuffer, int inBufferCount, char[] outBuf Debug.Assert(outBuffer != null, "Null output buffer"); Debug.Assert((outBufferOffset >= 0) && (outBufferCount > 0) && (outBufferOffset + outBufferCount <= outBuffer.Length), $"Bad outBufferCount: {outBufferCount} or outBufferOffset: {outBufferOffset}"); - int charsRead; - int bytesUsed; - bool completed; - _decoder.Convert(inBuffer, 0, inBufferCount, outBuffer, outBufferOffset, outBufferCount, false, out bytesUsed, out charsRead, out completed); + _decoder.Convert(inBuffer, 0, inBufferCount, outBuffer, outBufferOffset, outBufferCount, false, out int bytesUsed, out int charsRead, out bool completed); // completed may be false and there is no spare bytes if the Decoder has stored bytes to use later if ((!completed) && (bytesUsed < inBufferCount)) @@ -479,7 +470,7 @@ internal static void ValidateReadParameters(char[] buffer, int index, int count) sealed internal class SqlUnicodeEncoding : UnicodeEncoding { - private static SqlUnicodeEncoding s_singletonEncoding = new SqlUnicodeEncoding(); + private static readonly SqlUnicodeEncoding s_singletonEncoding = new(); private SqlUnicodeEncoding() : base(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: false) { } @@ -511,10 +502,7 @@ public override int GetCharCount(byte[] bytes, int index, int count) public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { // This method is required - simply call Convert() - int bytesUsed; - int charsUsed; - bool completed; - Convert(bytes, byteIndex, byteCount, chars, charIndex, chars.Length - charIndex, true, out bytesUsed, out charsUsed, out completed); + Convert(bytes, byteIndex, byteCount, chars, charIndex, chars.Length - charIndex, true, out int bytesUsed, out int charsUsed, out bool completed); return charsUsed; }