// // 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 } }