2016-08-03 10:59:49 +00:00
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 {
2017-08-21 15:34:15 +00:00
// We need more data - setup the callback, and mark this as not completed sync
2016-08-03 10:59:49 +00:00
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 ) ;
}
}
}
}