//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.IO;
using System.Text;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{
///
/// Reader for service buffer formatted file streams
///
public class ServiceBufferFileStreamReader : IFileStreamReader
{
private const int DefaultBufferSize = 8192;
#region Member Variables
private byte[] buffer;
private readonly IFileStreamWrapper fileStream;
#endregion
///
/// Constructs a new ServiceBufferFileStreamReader and initializes its state
///
/// The filestream wrapper to read from
/// The name of the file to read from
public ServiceBufferFileStreamReader(IFileStreamWrapper fileWrapper, string fileName)
{
// Open file for reading/writing
fileStream = fileWrapper;
fileStream.Init(fileName, DefaultBufferSize, FileAccess.Read);
// Create internal buffer
buffer = new byte[DefaultBufferSize];
}
#region IFileStreamStorage Implementation
///
/// Reads a row from the file, based on the columns provided
///
/// Offset into the file where the row starts
/// The columns that were encoded
/// The objects from the row
public object[] ReadRow(long fileOffset, IEnumerable columns)
{
// Initialize for the loop
long currentFileOffset = fileOffset;
List results = new List();
// Iterate over the columns
foreach (DbColumnWrapper column in columns)
{
// We will pivot based on the type of the column
Type colType;
if (column.IsSqlVariant)
{
// For SQL Variant columns, the type is written first in string format
FileStreamReadResult sqlVariantTypeResult = ReadString(currentFileOffset);
currentFileOffset += sqlVariantTypeResult.TotalLength;
// If the typename is null, then the whole value is null
if (sqlVariantTypeResult.IsNull)
{
results.Add(null);
continue;
}
// The typename is stored in the string
colType = Type.GetType(sqlVariantTypeResult.Value);
// Workaround .NET bug, see sqlbu# 440643 and vswhidbey# 599834
// TODO: Is this workaround necessary for .NET Core?
if (colType == null && sqlVariantTypeResult.Value == "System.Data.SqlTypes.SqlSingle")
{
colType = typeof(SqlSingle);
}
}
else
{
colType = column.DataType;
}
if (colType == typeof(string))
{
// String - most frequently used data type
FileStreamReadResult result = ReadString(currentFileOffset);
currentFileOffset += result.TotalLength;
results.Add(result.IsNull ? null : result.Value);
}
else if (colType == typeof(SqlString))
{
// SqlString
FileStreamReadResult result = ReadString(currentFileOffset);
currentFileOffset += result.TotalLength;
results.Add(result.IsNull ? null : (SqlString) result.Value);
}
else if (colType == typeof(short))
{
// Int16
FileStreamReadResult result = ReadInt16(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlInt16))
{
// SqlInt16
FileStreamReadResult result = ReadInt16(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlInt16)result.Value);
}
}
else if (colType == typeof(int))
{
// Int32
FileStreamReadResult result = ReadInt32(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlInt32))
{
// SqlInt32
FileStreamReadResult result = ReadInt32(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlInt32)result.Value);
}
}
else if (colType == typeof(long))
{
// Int64
FileStreamReadResult result = ReadInt64(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlInt64))
{
// SqlInt64
FileStreamReadResult result = ReadInt64(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlInt64)result.Value);
}
}
else if (colType == typeof(byte))
{
// byte
FileStreamReadResult result = ReadByte(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlByte))
{
// SqlByte
FileStreamReadResult result = ReadByte(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlByte)result.Value);
}
}
else if (colType == typeof(char))
{
// Char
FileStreamReadResult result = ReadChar(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(bool))
{
// Bool
FileStreamReadResult result = ReadBoolean(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlBoolean))
{
// SqlBoolean
FileStreamReadResult result = ReadBoolean(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlBoolean)result.Value);
}
}
else if (colType == typeof(double))
{
// double
FileStreamReadResult result = ReadDouble(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlDouble))
{
// SqlByte
FileStreamReadResult result = ReadDouble(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlDouble)result.Value);
}
}
else if (colType == typeof(float))
{
// float
FileStreamReadResult result = ReadSingle(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlSingle))
{
// SqlSingle
FileStreamReadResult result = ReadSingle(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlSingle)result.Value);
}
}
else if (colType == typeof(decimal))
{
// Decimal
FileStreamReadResult result = ReadDecimal(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlDecimal))
{
// SqlDecimal
FileStreamReadResult result = ReadSqlDecimal(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(DateTime))
{
// DateTime
FileStreamReadResult result = ReadDateTime(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlDateTime))
{
// SqlDateTime
FileStreamReadResult result = ReadDateTime(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add((SqlDateTime)result.Value);
}
}
else if (colType == typeof(DateTimeOffset))
{
// DateTimeOffset
FileStreamReadResult result = ReadDateTimeOffset(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(TimeSpan))
{
// TimeSpan
FileStreamReadResult result = ReadTimeSpan(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(byte[]))
{
// Byte Array
FileStreamReadResult result = ReadBytes(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull || (column.IsUdt && result.Value.Length == 0))
{
results.Add(null);
}
else
{
results.Add(result.Value);
}
}
else if (colType == typeof(SqlBytes))
{
// SqlBytes
FileStreamReadResult result = ReadBytes(currentFileOffset);
currentFileOffset += result.TotalLength;
results.Add(result.IsNull ? null : new SqlBytes(result.Value));
}
else if (colType == typeof(SqlBinary))
{
// SqlBinary
FileStreamReadResult result = ReadBytes(currentFileOffset);
currentFileOffset += result.TotalLength;
results.Add(result.IsNull ? null : new SqlBinary(result.Value));
}
else if (colType == typeof(SqlGuid))
{
// SqlGuid
FileStreamReadResult result = ReadBytes(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(new SqlGuid(result.Value));
}
}
else if (colType == typeof(SqlMoney))
{
// SqlMoney
FileStreamReadResult result = ReadDecimal(currentFileOffset);
currentFileOffset += result.TotalLength;
if (result.IsNull)
{
results.Add(null);
}
else
{
results.Add(new SqlMoney(result.Value));
}
}
else
{
// Treat everything else as a string
FileStreamReadResult result = ReadString(currentFileOffset);
currentFileOffset += result.TotalLength;
results.Add(result.IsNull ? null : result.Value);
}
}
return results.ToArray();
}
///
/// Reads a short from the file at the offset provided
///
/// Offset into the file to read the short from
/// A short
public FileStreamReadResult ReadInt16(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 2, "Invalid data length");
bool isNull = length.ValueLength == 0;
short val = default(short);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = BitConverter.ToInt16(buffer, 0);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a int from the file at the offset provided
///
/// Offset into the file to read the int from
/// An int
public FileStreamReadResult ReadInt32(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 4, "Invalid data length");
bool isNull = length.ValueLength == 0;
int val = default(int);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = BitConverter.ToInt32(buffer, 0);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a long from the file at the offset provided
///
/// Offset into the file to read the long from
/// A long
public FileStreamReadResult ReadInt64(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 8, "Invalid data length");
bool isNull = length.ValueLength == 0;
long val = default(long);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = BitConverter.ToInt64(buffer, 0);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a byte from the file at the offset provided
///
/// Offset into the file to read the byte from
/// A byte
public FileStreamReadResult ReadByte(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 1, "Invalid data length");
bool isNull = length.ValueLength == 0;
byte val = default(byte);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = buffer[0];
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a char from the file at the offset provided
///
/// Offset into the file to read the char from
/// A char
public FileStreamReadResult ReadChar(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 2, "Invalid data length");
bool isNull = length.ValueLength == 0;
char val = default(char);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = BitConverter.ToChar(buffer, 0);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a bool from the file at the offset provided
///
/// Offset into the file to read the bool from
/// A bool
public FileStreamReadResult ReadBoolean(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 1, "Invalid data length");
bool isNull = length.ValueLength == 0;
bool val = default(bool);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = buffer[0] == 0x01;
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a single from the file at the offset provided
///
/// Offset into the file to read the single from
/// A single
public FileStreamReadResult ReadSingle(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 4, "Invalid data length");
bool isNull = length.ValueLength == 0;
float val = default(float);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = BitConverter.ToSingle(buffer, 0);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a double from the file at the offset provided
///
/// Offset into the file to read the double from
/// A double
public FileStreamReadResult ReadDouble(long fileOffset)
{
LengthResult length = ReadLength(fileOffset);
Debug.Assert(length.ValueLength == 0 || length.ValueLength == 8, "Invalid data length");
bool isNull = length.ValueLength == 0;
double val = default(double);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
val = BitConverter.ToDouble(buffer, 0);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a SqlDecimal from the file at the offset provided
///
/// Offset into the file to read the SqlDecimal from
/// A SqlDecimal
public FileStreamReadResult ReadSqlDecimal(long offset)
{
LengthResult length = ReadLength(offset);
Debug.Assert(length.ValueLength == 0 || (length.ValueLength - 3)%4 == 0,
string.Format("Invalid data length: {0}", length.ValueLength));
bool isNull = length.ValueLength == 0;
SqlDecimal val = default(SqlDecimal);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
int[] arrInt32 = new int[(length.ValueLength - 3)/4];
Buffer.BlockCopy(buffer, 3, arrInt32, 0, length.ValueLength - 3);
val = new SqlDecimal(buffer[0], buffer[1], 1 == buffer[2], arrInt32);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a decimal from the file at the offset provided
///
/// Offset into the file to read the decimal from
/// A decimal
public FileStreamReadResult ReadDecimal(long offset)
{
LengthResult length = ReadLength(offset);
Debug.Assert(length.ValueLength%4 == 0, "Invalid data length");
bool isNull = length.ValueLength == 0;
decimal val = default(decimal);
if (!isNull)
{
fileStream.ReadData(buffer, length.ValueLength);
int[] arrInt32 = new int[length.ValueLength/4];
Buffer.BlockCopy(buffer, 0, arrInt32, 0, length.ValueLength);
val = new decimal(arrInt32);
}
return new FileStreamReadResult(val, length.TotalLength, isNull);
}
///
/// Reads a DateTime from the file at the offset provided
///
/// Offset into the file to read the DateTime from
/// A DateTime
public FileStreamReadResult ReadDateTime(long offset)
{
FileStreamReadResult ticks = ReadInt64(offset);
DateTime val = default(DateTime);
if (!ticks.IsNull)
{
val = new DateTime(ticks.Value);
}
return new FileStreamReadResult(val, ticks.TotalLength, ticks.IsNull);
}
///
/// Reads a DateTimeOffset from the file at the offset provided
///
/// Offset into the file to read the DateTimeOffset from
/// A DateTimeOffset
public FileStreamReadResult ReadDateTimeOffset(long offset)
{
// DateTimeOffset is represented by DateTime.Ticks followed by TimeSpan.Ticks
// both as Int64 values
// read the DateTime ticks
DateTimeOffset val = default(DateTimeOffset);
FileStreamReadResult dateTimeTicks = ReadInt64(offset);
int totalLength = dateTimeTicks.TotalLength;
if (dateTimeTicks.TotalLength > 0 && !dateTimeTicks.IsNull)
{
// read the TimeSpan ticks
FileStreamReadResult timeSpanTicks = ReadInt64(offset + dateTimeTicks.TotalLength);
Debug.Assert(!timeSpanTicks.IsNull, "TimeSpan ticks cannot be null if DateTime ticks are not null!");
totalLength += timeSpanTicks.TotalLength;
// build the DateTimeOffset
val = new DateTimeOffset(new DateTime(dateTimeTicks.Value), new TimeSpan(timeSpanTicks.Value));
}
return new FileStreamReadResult(val, totalLength, dateTimeTicks.IsNull);
}
///
/// Reads a TimeSpan from the file at the offset provided
///
/// Offset into the file to read the TimeSpan from
/// A TimeSpan
public FileStreamReadResult ReadTimeSpan(long offset)
{
FileStreamReadResult timeSpanTicks = ReadInt64(offset);
TimeSpan val = default(TimeSpan);
if (!timeSpanTicks.IsNull)
{
val = new TimeSpan(timeSpanTicks.Value);
}
return new FileStreamReadResult(val, timeSpanTicks.TotalLength, timeSpanTicks.IsNull);
}
///
/// Reads a string from the file at the offset provided
///
/// Offset into the file to read the string from
/// A string
public FileStreamReadResult ReadString(long offset)
{
LengthResult fieldLength = ReadLength(offset);
Debug.Assert(fieldLength.ValueLength%2 == 0, "Invalid data length");
if (fieldLength.ValueLength == 0) // there is no data
{
// If the total length is 5 (5 bytes for length, 0 for value), then the string is empty
// Otherwise, the string is null
bool isNull = fieldLength.TotalLength != 5;
return new FileStreamReadResult(isNull ? null : string.Empty,
fieldLength.TotalLength, isNull);
}
// positive length
AssureBufferLength(fieldLength.ValueLength);
fileStream.ReadData(buffer, fieldLength.ValueLength);
return new FileStreamReadResult(Encoding.Unicode.GetString(buffer, 0, fieldLength.ValueLength), fieldLength.TotalLength, false);
}
///
/// Reads bytes from the file at the offset provided
///
/// Offset into the file to read the bytes from
/// A byte array
public FileStreamReadResult ReadBytes(long offset)
{
LengthResult fieldLength = ReadLength(offset);
if (fieldLength.ValueLength == 0)
{
// If the total length is 5 (5 bytes for length, 0 for value), then the byte array is 0x
// Otherwise, the byte array is null
bool isNull = fieldLength.TotalLength != 5;
return new FileStreamReadResult(isNull ? null : new byte[0],
fieldLength.TotalLength, isNull);
}
// positive length
byte[] val = new byte[fieldLength.ValueLength];
fileStream.ReadData(val, fieldLength.ValueLength);
return new FileStreamReadResult(val, fieldLength.TotalLength, false);
}
///
/// Reads the length of a field at the specified offset in the file
///
/// Offset into the file to read the field length from
/// A LengthResult
internal LengthResult ReadLength(long offset)
{
// read in length information
int lengthValue;
int lengthLength = fileStream.ReadData(buffer, 1, offset);
if (buffer[0] != 0xFF)
{
// one byte is enough
lengthValue = Convert.ToInt32(buffer[0]);
}
else
{
// read in next 4 bytes
lengthLength += fileStream.ReadData(buffer, 4);
// reconstruct the length
lengthValue = BitConverter.ToInt32(buffer, 0);
}
return new LengthResult {LengthLength = lengthLength, ValueLength = lengthValue};
}
#endregion
///
/// Internal struct used for representing the length of a field from the file
///
internal struct LengthResult
{
///
/// How many bytes the length takes up
///
public int LengthLength { get; set; }
///
/// How many bytes the value takes up
///
public int ValueLength { get; set; }
///
/// +
///
public int TotalLength
{
get { return LengthLength + ValueLength; }
}
}
///
/// Creates a new buffer that is of the specified length if the buffer is not already
/// at least as long as specified.
///
/// The minimum buffer size
private void AssureBufferLength(int newBufferLength)
{
if (buffer.Length < newBufferLength)
{
buffer = new byte[newBufferLength];
}
}
#region IDisposable Implementation
private bool disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
fileStream.Dispose();
}
disposed = true;
}
~ServiceBufferFileStreamReader()
{
Dispose(false);
}
#endregion
}
}