| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- using System;
- using System.Data.Common;
- using System.Diagnostics;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- namespace System.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)), string.Format("Bad peeked character: {0}", _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)), string.Format("Bad read character: {0}", 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), string.Format("Bad peeked character: {0}", _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<int> ReadAsync(char[] buffer, int index, int count)
- {
- ValidateReadParameters(buffer, index, count);
- TaskCompletionSource<int> completion = new TaskCompletionSource<int>();
- if (IsClosed)
- {
- completion.SetException(ADP.ExceptionWithStackTrace(ADP.ObjectDisposed(this)));
- }
- else
- {
- try
- {
- Task original = Interlocked.CompareExchange<Task>(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), string.Format("Bad peeked character: {0}", _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<int> 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 [....]
- 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);
- }
- /// <summary>
- /// 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
- /// </summary>
- internal void SetClosed()
- {
- _disposalTokenSource.Cancel();
- _reader = null;
- _peekedChar = -1;
- // Wait for pending task
- var currentTask = _currentTask;
- if (currentTask != null)
- {
- ((IAsyncResult)currentTask).AsyncWaitHandle.WaitOne();
- }
- }
- /// <summary>
- /// 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())
- /// </summary>
- /// <param name="buffer"></param>
- /// <param name="index"></param>
- /// <param name="count"></param>
- /// <returns></returns>
- 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), string.Format("Bad count: {0} or index: {1}", count, 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);
- }
- }
- /// <summary>
- /// Creates a byte array large enough to store all bytes for the characters in the current encoding, then fills it with any leftover bytes
- /// </summary>
- /// <param name="numberOfChars">Number of characters that are to be read</param>
- /// <param name="byteBufferUsed">Number of bytes pre-filled by the leftover bytes</param>
- /// <returns>A byte array of the correct size, pre-filled with leftover bytes</returns>
- 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];
- Array.Copy(_leftOverBytes, byteBuffer, _leftOverBytes.Length);
- byteBufferUsed = _leftOverBytes.Length;
- }
- }
- else
- {
- byteBuffer = new byte[byteBufferSize];
- byteBufferUsed = 0;
- }
- }
- return byteBuffer;
- }
- /// <summary>
- /// Decodes the given bytes into characters, and stores the leftover bytes for later use
- /// </summary>
- /// <param name="inBuffer">Buffer of bytes to decode</param>
- /// <param name="inBufferCount">Number of bytes to decode from the inBuffer</param>
- /// <param name="outBuffer">Buffer to write the characters to</param>
- /// <param name="outBufferOffset">Offset to start writing to outBuffer at</param>
- /// <param name="outBufferCount">Maximum number of characters to decode</param>
- /// <returns>The actual number of characters decoded</returns>
- 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), string.Format("Bad inBufferCount: {0}", inBufferCount));
- Debug.Assert(outBuffer != null, "Null output buffer");
- Debug.Assert((outBufferOffset >= 0) && (outBufferCount > 0) && (outBufferOffset + outBufferCount <= outBuffer.Length), string.Format("Bad outBufferCount: {0} or outBufferOffset: {1}", outBufferCount, 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];
- Array.Copy(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;
- }
- /// <summary>
- /// True if this TextReader is supposed to be closed
- /// </summary>
- private bool IsClosed
- {
- get { return (_reader == null); }
- }
- /// <summary>
- /// True if there is data left to read
- /// </summary>
- /// <returns></returns>
- private bool IsDataLeft
- {
- get { return ((_leftOverBytes != null) || (_reader.ColumnDataBytesRemaining() > 0)); }
- }
- /// <summary>
- /// True if there is a peeked character available
- /// </summary>
- private bool HasPeekedChar
- {
- get { return (_peekedChar >= char.MinValue); }
- }
- /// <summary>
- /// Checks the the parameters passed into a Read() method are valid
- /// </summary>
- /// <param name="buffer"></param>
- /// <param name="index"></param>
- /// <param name="count"></param>
- internal static void ValidateReadParameters(char[] buffer, int index, int count)
- {
- if (buffer == null)
- {
- throw ADP.ArgumentNull(ADP.ParameterBuffer);
- }
- if (index < 0)
- {
- throw ADP.ArgumentOutOfRange(ADP.ParameterIndex);
- }
- if (count < 0)
- {
- throw ADP.ArgumentOutOfRange(ADP.ParameterCount);
- }
- 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);
- }
- }
- }
- }
|