Move Save As to ResultSet (#181)

It's an overhaul of the Save As mechanism to utilize the file reader/writer classes to better align with the patterns laid out by the rest of the query execution. Why make this change? This change makes our code base more uniform and adherent to the patterns/paradigms we've set up. This change also helps with the encapsulation of the classes to "separate the concerns" of each component of the save as function. 

* Replumbing the save as execution to pass the call down the query stack as QueryExecutionService->Query->Batch->ResultSet
    * Each layer performs it's own parameter checking
        * QueryExecutionService checks if the query exists
        * Query checks if the batch exists
        * Batch checks if the result set exists
        * ResultSet checks if the row counts are valid and if the result set has been executed
    * Success/Failure delegates are passed down the chain as well
* Determination of whether a save request is a "selection" moved to the SaveResultsRequest class to eliminate duplication of code and creation of utility classes
* Making the IFileStream* classes more generic
    * Removing the requirements of max characters to store from the GetWriter method, and moving it into the constructor for the temporary buffer writer - the values have been moved to the settings and given defaults
    * Removing the individual type writers from IFileStreamWriter
    * Removing the individual type writers from IFIleStreamReader
* Adding a new overload for WriteRow to IFileStreamWriter that will write out data, given a row's worth of data and the list of columns
* Creating a new IFileStreamFactory that creates a reader/writer pair for reading from the temporary files and writing to CSV files
* Creating a new IFileStreamFactory that creates a reader/writer pair for reading from the temporary files and writing to JSON files
* Dramatically simplified the CSV encoding functionality
* Removed duplicated logic for saving in different types and condensed down to a single chain that only differs based on what type of factory is provided
* Removing the logic for managing the list of save as tasks, since the ResultSet now performs the actual saving work, there's no real need to expose the internals of the ResultSet
* Adding new strings to the sr.strings file for save as error messages
* Completely rewriting the unit tests for the save as mechanism. Very fine grained unit tests now that should cover majority of cases (aside from race conditions)


* Refactoring maxchars params into settings and out of file stream factory

* Removing write*/read* methods from file stream readers/writers

* Migrating the CSV save as to the resultset

* Tweaks to unit testing to eliminate writing files to disk

* WIP, moving to a base class for save results writers

* Everything is wired up and compiles

* Adding unit tests for CSV encoding

* Adding unit tests for CSV and Json writers

* Adding tests to the result set for saving

* Refactor to throw exceptions on errors instead of calling failure handler

* Unit tests for batch/query argument in range

* Unit tests

* Adding service integration unit tests

* Final polish, copyright notices, etc

* Adding NULL logic

* Fixing issue of unicode to utf8

* Fixing issues as per @kburtram code review comments

* Adding files that got broken?
This commit is contained in:
Benjamin Russell
2016-12-21 17:52:34 -08:00
committed by GitHub
parent adc9672fa3
commit 7ea1b1bb87
29 changed files with 1880 additions and 918 deletions

View File

@@ -14,7 +14,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
IFileStreamReader GetReader(string fileName);
IFileStreamWriter GetWriter(string fileName, int maxCharsToStore, int maxXmlCharsToStore);
IFileStreamWriter GetWriter(string fileName);
void DisposeFile(string fileName);

View File

@@ -4,7 +4,8 @@
//
using System;
using System.Data.SqlTypes;
using System.Collections.Generic;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{
@@ -14,24 +15,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
public interface IFileStreamWriter : IDisposable
{
int WriteRow(StorageDataReader dataReader);
int WriteNull();
int WriteInt16(short val);
int WriteInt32(int val);
int WriteInt64(long val);
int WriteByte(byte val);
int WriteChar(char val);
int WriteBoolean(bool val);
int WriteSingle(float val);
int WriteDouble(double val);
int WriteDecimal(decimal val);
int WriteSqlDecimal(SqlDecimal val);
int WriteDateTime(DateTime val);
int WriteDateTimeOffset(DateTimeOffset dtoVal);
int WriteTimeSpan(TimeSpan val);
int WriteString(string val);
int WriteBytes(byte[] bytes);
int WriteGuid(Guid val);
int WriteMoney(SqlMoney val);
void WriteRow(IList<DbCellValue> row, IList<DbColumnWrapper> columns);
void FlushBuffer();
}
}

View File

@@ -0,0 +1,66 @@
//
// 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.IO;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{
/// <summary>
/// Factory for creating a reader/writer pair that will read from the temporary buffer file
/// and output to a CSV file.
/// </summary>
public class SaveAsCsvFileStreamFactory : IFileStreamFactory
{
#region Properties
/// <summary>
/// Parameters for the save as CSV request
/// </summary>
public SaveResultsAsCsvRequestParams SaveRequestParams { get; set; }
#endregion
/// <summary>
/// File names are not meant to be created with this factory.
/// </summary>
/// <exception cref="NotImplementedException">Thrown all times</exception>
[Obsolete]
public string CreateFile()
{
throw new NotImplementedException();
}
/// <summary>
/// Returns a new service buffer reader for reading results back in from the temporary buffer files
/// </summary>
/// <param name="fileName">Path to the temp buffer file</param>
/// <returns>Stream reader</returns>
public IFileStreamReader GetReader(string fileName)
{
return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read));
}
/// <summary>
/// Returns a new CSV writer for writing results to a CSV file
/// </summary>
/// <param name="fileName">Path to the CSV output file</param>
/// <returns>Stream writer</returns>
public IFileStreamWriter GetWriter(string fileName)
{
return new SaveAsCsvFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite), SaveRequestParams);
}
/// <summary>
/// Safely deletes the file
/// </summary>
/// <param name="fileName">Path to the file to delete</param>
public void DisposeFile(string fileName)
{
FileUtils.SafeFileDelete(fileName);
}
}
}

View File

@@ -0,0 +1,118 @@
//
// 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.IO;
using System.Linq;
using System.Text;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{
/// <summary>
/// Writer for writing rows of results to a CSV file
/// </summary>
public class SaveAsCsvFileStreamWriter : SaveAsStreamWriter
{
#region Member Variables
private readonly SaveResultsAsCsvRequestParams saveParams;
private bool headerWritten;
#endregion
/// <summary>
/// Constructor, stores the CSV specific request params locally, chains into the base
/// constructor
/// </summary>
/// <param name="stream">FileStream to access the CSV file output</param>
/// <param name="requestParams">CSV save as request parameters</param>
public SaveAsCsvFileStreamWriter(Stream stream, SaveResultsAsCsvRequestParams requestParams)
: base(stream, requestParams)
{
saveParams = requestParams;
}
/// <summary>
/// Writes a row of data as a CSV row. If this is the first row and the user has requested
/// it, the headers for the column will be emitted as well.
/// </summary>
/// <param name="row">The data of the row to output to the file</param>
/// <param name="columns">
/// The entire list of columns for the result set. They will be filtered down as per the
/// request params.
/// </param>
public override void WriteRow(IList<DbCellValue> row, IList<DbColumnWrapper> columns)
{
// Write out the header if we haven't already and the user chose to have it
if (saveParams.IncludeHeaders && !headerWritten)
{
// Build the string
var selectedColumns = columns.Skip(ColumnStartIndex ?? 0).Take(ColumnCount ?? columns.Count)
.Select(c => EncodeCsvField(c.ColumnName) ?? string.Empty);
string headerLine = string.Join(",", selectedColumns);
// Encode it and write it out
byte[] headerBytes = Encoding.UTF8.GetBytes(headerLine + Environment.NewLine);
FileStream.Write(headerBytes, 0, headerBytes.Length);
headerWritten = true;
}
// Build the string for the row
var selectedCells = row.Skip(ColumnStartIndex ?? 0)
.Take(ColumnCount ?? columns.Count)
.Select(c => EncodeCsvField(c.DisplayValue));
string rowLine = string.Join(",", selectedCells);
// Encode it and write it out
byte[] rowBytes = Encoding.UTF8.GetBytes(rowLine + Environment.NewLine);
FileStream.Write(rowBytes, 0, rowBytes.Length);
}
/// <summary>
/// Encodes a single field for inserting into a CSV record. The following rules are applied:
/// <list type="bullet">
/// <item><description>All double quotes (") are replaced with a pair of consecutive double quotes</description></item>
/// </list>
/// The entire field is also surrounded by a pair of double quotes if any of the following conditions are met:
/// <list type="bullet">
/// <item><description>The field begins or ends with a space</description></item>
/// <item><description>The field begins or ends with a tab</description></item>
/// <item><description>The field contains the ListSeparator string</description></item>
/// <item><description>The field contains the '\n' character</description></item>
/// <item><description>The field contains the '\r' character</description></item>
/// <item><description>The field contains the '"' character</description></item>
/// </list>
/// </summary>
/// <param name="field">The field to encode</param>
/// <returns>The CSV encoded version of the original field</returns>
internal static string EncodeCsvField(string field)
{
// Special case for nulls
if (field == null)
{
return "NULL";
}
// Whether this field has special characters which require it to be embedded in quotes
bool embedInQuotes = field.IndexOfAny(new[] {',', '\r', '\n', '"'}) >= 0 // Contains special characters
|| field.StartsWith(" ") || field.EndsWith(" ") // Start/Ends with space
|| field.StartsWith("\t") || field.EndsWith("\t"); // Starts/Ends with tab
//Replace all quotes in the original field with double quotes
string ret = field.Replace("\"", "\"\"");
if (embedInQuotes)
{
ret = $"\"{ret}\"";
}
return ret;
}
}
}

View File

@@ -0,0 +1,64 @@
//
// 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.IO;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{
public class SaveAsJsonFileStreamFactory : IFileStreamFactory
{
#region Properties
/// <summary>
/// Parameters for the save as JSON request
/// </summary>
public SaveResultsAsJsonRequestParams SaveRequestParams { get; set; }
#endregion
/// <summary>
/// File names are not meant to be created with this factory.
/// </summary>
/// <exception cref="NotImplementedException">Thrown all times</exception>
[Obsolete]
public string CreateFile()
{
throw new NotImplementedException();
}
/// <summary>
/// Returns a new service buffer reader for reading results back in from the temporary buffer files
/// </summary>
/// <param name="fileName">Path to the temp buffer file</param>
/// <returns>Stream reader</returns>
public IFileStreamReader GetReader(string fileName)
{
return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read));
}
/// <summary>
/// Returns a new JSON writer for writing results to a JSON file
/// </summary>
/// <param name="fileName">Path to the JSON output file</param>
/// <returns>Stream writer</returns>
public IFileStreamWriter GetWriter(string fileName)
{
return new SaveAsJsonFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite), SaveRequestParams);
}
/// <summary>
/// Safely deletes the file
/// </summary>
/// <param name="fileName">Path to the file to delete</param>
public void DisposeFile(string fileName)
{
FileUtils.SafeFileDelete(fileName);
}
}
}

View File

@@ -0,0 +1,93 @@
//
// 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.IO;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Newtonsoft.Json;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{
/// <summary>
/// Writer for writing rows of results to a JSON file.
/// </summary>
/// <remarks>
/// This implements its own IDisposable because the cleanup logic closes the array that was
/// created when the writer was created. Since this behavior is different than the standard
/// file stream cleanup, the extra Dispose method was added.
/// </remarks>
public class SaveAsJsonFileStreamWriter : SaveAsStreamWriter, IDisposable
{
#region Member Variables
private readonly StreamWriter streamWriter;
private readonly JsonWriter jsonWriter;
#endregion
/// <summary>
/// Constructor, writes the header to the file, chains into the base constructor
/// </summary>
/// <param name="stream">FileStream to access the JSON file output</param>
/// <param name="requestParams">JSON save as request parameters</param>
public SaveAsJsonFileStreamWriter(Stream stream, SaveResultsRequestParams requestParams)
: base(stream, requestParams)
{
// Setup the internal state
streamWriter = new StreamWriter(stream);
jsonWriter = new JsonTextWriter(streamWriter);
// Write the header of the file
jsonWriter.WriteStartArray();
}
/// <summary>
/// Writes a row of data as a JSON object
/// </summary>
/// <param name="row">The data of the row to output to the file</param>
/// <param name="columns">
/// The entire list of columns for the result set. They will be filtered down as per the
/// request params.
/// </param>
public override void WriteRow(IList<DbCellValue> row, IList<DbColumnWrapper> columns)
{
// Write the header for the object
jsonWriter.WriteStartObject();
// Write the items out as properties
int columnStart = ColumnStartIndex ?? 0;
int columnEnd = ColumnEndIndex ?? columns.Count;
for (int i = columnStart; i < columnEnd; i++)
{
jsonWriter.WritePropertyName(columns[i].ColumnName);
if (row[i].RawObject == null)
{
jsonWriter.WriteNull();
}
else
{
jsonWriter.WriteValue(row[i].DisplayValue);
}
}
// Write the footer for the object
jsonWriter.WriteEndObject();
}
/// <summary>
/// Disposes the writer by closing up the array that contains the row objects
/// </summary>
public new void Dispose()
{
// Write the footer of the file
jsonWriter.WriteEndArray();
jsonWriter.Close();
streamWriter.Dispose();
base.Dispose();
}
}
}

View File

@@ -0,0 +1,113 @@
//
// 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.IO;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{
/// <summary>
/// Abstract class for implementing writers that save results to file. Stores some basic info
/// that all save as writer would need.
/// </summary>
public abstract class SaveAsStreamWriter : IFileStreamWriter
{
/// <summary>
/// Stores the internal state for the writer that will be necessary for any writer.
/// </summary>
/// <param name="stream">The stream that will be written to</param>
/// <param name="requestParams">The SaveAs request parameters</param>
protected SaveAsStreamWriter(Stream stream, SaveResultsRequestParams requestParams)
{
FileStream = stream;
var saveParams = requestParams;
if (requestParams.IsSaveSelection)
{
// ReSharper disable PossibleInvalidOperationException IsSaveSelection verifies these values exist
ColumnStartIndex = saveParams.ColumnStartIndex.Value;
ColumnEndIndex = saveParams.ColumnEndIndex.Value;
ColumnCount = saveParams.ColumnEndIndex.Value - saveParams.ColumnStartIndex.Value + 1;
// ReSharper restore PossibleInvalidOperationException
}
}
#region Properties
/// <summary>
/// Index of the first column to write to the output file
/// </summary>
protected int? ColumnStartIndex { get; private set; }
/// <summary>
/// Number of columns to write to the output file
/// </summary>
protected int? ColumnCount { get; private set; }
/// <summary>
/// Index of the last column to write to the output file
/// </summary>
protected int? ColumnEndIndex { get; private set; }
/// <summary>
/// The file stream to use to write the output file
/// </summary>
protected Stream FileStream { get; private set; }
#endregion
/// <summary>
/// Not implemented, do not use.
/// </summary>
[Obsolete]
public int WriteRow(StorageDataReader dataReader)
{
throw new InvalidOperationException("This type of writer is meant to write values from a list of cell values only.");
}
/// <summary>
/// Writes a row of data to the output file using the format provided by the implementing class.
/// </summary>
/// <param name="row">The row of data to output</param>
/// <param name="columns">The list of columns to output</param>
public abstract void WriteRow(IList<DbCellValue> row, IList<DbColumnWrapper> columns);
/// <summary>
/// Flushes the file stream buffer
/// </summary>
public void FlushBuffer()
{
FileStream.Flush();
}
#region IDisposable Implementation
private bool disposed;
/// <summary>
/// Disposes the instance by flushing and closing the file stream
/// </summary>
/// <param name="disposing"></param>
private void Dispose(bool disposing)
{
if (disposed || !disposing)
{
disposed = true;
return;
}
FileStream.Flush();
FileStream.Dispose();
}
public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@@ -12,6 +12,20 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// </summary>
public class ServiceBufferFileStreamFactory : IFileStreamFactory
{
#region Properties
/// <summary>
/// The maximum number of characters to store from long text fields
/// </summary>
public int MaxCharsToStore { get; set; }
/// <summary>
/// The maximum number of characters to store from xml fields
/// </summary>
public int MaxXmlCharsToStore { get; set; }
#endregion
/// <summary>
/// Creates a new temporary file
/// </summary>
@@ -37,12 +51,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// SSMS formatted buffer file
/// </summary>
/// <param name="fileName">The file to write values to</param>
/// <param name="maxCharsToStore">The maximum number of characters to store from long text fields</param>
/// <param name="maxXmlCharsToStore">The maximum number of characters to store from xml fields</param>
/// <returns>A <see cref="ServiceBufferFileStreamWriter"/></returns>
public IFileStreamWriter GetWriter(string fileName, int maxCharsToStore, int maxXmlCharsToStore)
public IFileStreamWriter GetWriter(string fileName)
{
return new ServiceBufferFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite), maxCharsToStore, maxXmlCharsToStore);
return new ServiceBufferFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite), MaxCharsToStore, MaxXmlCharsToStore);
}
/// <summary>

View File

@@ -150,6 +150,58 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
return results;
}
#endregion
#region Private Helpers
/// <summary>
/// Creates a new buffer that is of the specified length if the buffer is not already
/// at least as long as specified.
/// </summary>
/// <param name="newBufferLength">The minimum buffer size</param>
private void AssureBufferLength(int newBufferLength)
{
if (buffer.Length < newBufferLength)
{
buffer = new byte[newBufferLength];
}
}
/// <summary>
/// Reads the value of a cell from the file wrapper, checks to see if it null using
/// <paramref name="isNullFunc"/>, and converts it to the proper output type using
/// <paramref name="convertFunc"/>.
/// </summary>
/// <param name="offset">Offset into the file to read from</param>
/// <param name="convertFunc">Function to use to convert the buffer to the target type</param>
/// <param name="isNullFunc">
/// If provided, this function will be used to determine if the value is null
/// </param>
/// <param name="toStringFunc">Optional function to use to convert the object to a string.</param>
/// <typeparam name="T">The expected type of the cell. Used to keep the code honest</typeparam>
/// <returns>The object, a display value, and the length of the value + its length</returns>
private FileStreamReadResult ReadCellHelper<T>(long offset, Func<int, T> convertFunc, Func<int, bool> isNullFunc = null, Func<T, string> toStringFunc = null)
{
LengthResult length = ReadLength(offset);
DbCellValue result = new DbCellValue();
if (isNullFunc == null ? length.ValueLength == 0 : isNullFunc(length.TotalLength))
{
result.RawObject = null;
result.DisplayValue = null;
}
else
{
AssureBufferLength(length.ValueLength);
fileStream.Read(buffer, 0, length.ValueLength);
T resultObject = convertFunc(length.ValueLength);
result.RawObject = resultObject;
result.DisplayValue = toStringFunc == null ? result.RawObject.ToString() : toStringFunc(resultObject);
}
return new FileStreamReadResult(result, length.TotalLength);
}
/// <summary>
/// Reads a short from the file at the offset provided
/// </summary>
@@ -317,7 +369,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
return ReadCellHelper(offset, length => {
long dtTicks = BitConverter.ToInt64(buffer, 0);
long dtOffset = BitConverter.ToInt64(buffer, 8);
return new DateTimeOffset(new DateTime(dtTicks), new TimeSpan(dtOffset));
return new DateTimeOffset(new DateTime(dtTicks), new TimeSpan(dtOffset));
});
}
@@ -408,7 +460,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// </summary>
/// <param name="offset">Offset into the file to read the field length from</param>
/// <returns>A LengthResult</returns>
internal LengthResult ReadLength(long offset)
private LengthResult ReadLength(long offset)
{
// read in length information
int lengthValue;
@@ -428,59 +480,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
lengthValue = BitConverter.ToInt32(buffer, 0);
}
return new LengthResult {LengthLength = lengthLength, ValueLength = lengthValue};
}
#endregion
#region Private Helpers
/// <summary>
/// Creates a new buffer that is of the specified length if the buffer is not already
/// at least as long as specified.
/// </summary>
/// <param name="newBufferLength">The minimum buffer size</param>
private void AssureBufferLength(int newBufferLength)
{
if (buffer.Length < newBufferLength)
{
buffer = new byte[newBufferLength];
}
}
/// <summary>
/// Reads the value of a cell from the file wrapper, checks to see if it null using
/// <paramref name="isNullFunc"/>, and converts it to the proper output type using
/// <paramref name="convertFunc"/>.
/// </summary>
/// <param name="offset">Offset into the file to read from</param>
/// <param name="convertFunc">Function to use to convert the buffer to the target type</param>
/// <param name="isNullFunc">
/// If provided, this function will be used to determine if the value is null
/// </param>
/// <param name="toStringFunc">Optional function to use to convert the object to a string.</param>
/// <typeparam name="T">The expected type of the cell. Used to keep the code honest</typeparam>
/// <returns>The object, a display value, and the length of the value + its length</returns>
private FileStreamReadResult ReadCellHelper<T>(long offset, Func<int, T> convertFunc, Func<int, bool> isNullFunc = null, Func<T, string> toStringFunc = null)
{
LengthResult length = ReadLength(offset);
DbCellValue result = new DbCellValue();
if (isNullFunc == null ? length.ValueLength == 0 : isNullFunc(length.TotalLength))
{
result.RawObject = null;
result.DisplayValue = null;
}
else
{
AssureBufferLength(length.ValueLength);
fileStream.Read(buffer, 0, length.ValueLength);
T resultObject = convertFunc(length.ValueLength);
result.RawObject = resultObject;
result.DisplayValue = toStringFunc == null ? result.RawObject.ToString() : toStringFunc(resultObject);
}
return new FileStreamReadResult(result, length.TotalLength);
return new LengthResult { LengthLength = lengthLength, ValueLength = lengthValue };
}
#endregion

View File

@@ -207,115 +207,133 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
return rowBytes;
}
[Obsolete]
public void WriteRow(IList<DbCellValue> row, IList<DbColumnWrapper> columns)
{
throw new InvalidOperationException("This type of writer is meant to write values from a DbDataReader only.");
}
/// <summary>
/// Flushes the internal buffer to the file stream
/// </summary>
public void FlushBuffer()
{
fileStream.Flush();
}
#endregion
#region Private Helpers
/// <summary>
/// Writes null to the file as one 0x00 byte
/// </summary>
/// <returns>Number of bytes used to store the null</returns>
public int WriteNull()
internal int WriteNull()
{
byteBuffer[0] = 0x00;
return WriteHelper(byteBuffer, 1);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 1);
}
/// <summary>
/// Writes a short to the file
/// </summary>
/// <returns>Number of bytes used to store the short</returns>
public int WriteInt16(short val)
internal int WriteInt16(short val)
{
byteBuffer[0] = 0x02; // length
shortBuffer[0] = val;
Buffer.BlockCopy(shortBuffer, 0, byteBuffer, 1, 2);
return WriteHelper(byteBuffer, 3);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 3);
}
/// <summary>
/// Writes a int to the file
/// </summary>
/// <returns>Number of bytes used to store the int</returns>
public int WriteInt32(int val)
internal int WriteInt32(int val)
{
byteBuffer[0] = 0x04; // length
intBuffer[0] = val;
Buffer.BlockCopy(intBuffer, 0, byteBuffer, 1, 4);
return WriteHelper(byteBuffer, 5);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 5);
}
/// <summary>
/// Writes a long to the file
/// </summary>
/// <returns>Number of bytes used to store the long</returns>
public int WriteInt64(long val)
internal int WriteInt64(long val)
{
byteBuffer[0] = 0x08; // length
longBuffer[0] = val;
Buffer.BlockCopy(longBuffer, 0, byteBuffer, 1, 8);
return WriteHelper(byteBuffer, 9);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 9);
}
/// <summary>
/// Writes a char to the file
/// </summary>
/// <returns>Number of bytes used to store the char</returns>
public int WriteChar(char val)
internal int WriteChar(char val)
{
byteBuffer[0] = 0x02; // length
charBuffer[0] = val;
Buffer.BlockCopy(charBuffer, 0, byteBuffer, 1, 2);
return WriteHelper(byteBuffer, 3);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 3);
}
/// <summary>
/// Writes a bool to the file
/// </summary>
/// <returns>Number of bytes used to store the bool</returns>
public int WriteBoolean(bool val)
internal int WriteBoolean(bool val)
{
byteBuffer[0] = 0x01; // length
byteBuffer[1] = (byte) (val ? 0x01 : 0x00);
return WriteHelper(byteBuffer, 2);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 2);
}
/// <summary>
/// Writes a byte to the file
/// </summary>
/// <returns>Number of bytes used to store the byte</returns>
public int WriteByte(byte val)
internal int WriteByte(byte val)
{
byteBuffer[0] = 0x01; // length
byteBuffer[1] = val;
return WriteHelper(byteBuffer, 2);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 2);
}
/// <summary>
/// Writes a float to the file
/// </summary>
/// <returns>Number of bytes used to store the float</returns>
public int WriteSingle(float val)
internal int WriteSingle(float val)
{
byteBuffer[0] = 0x04; // length
floatBuffer[0] = val;
Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 1, 4);
return WriteHelper(byteBuffer, 5);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 5);
}
/// <summary>
/// Writes a double to the file
/// </summary>
/// <returns>Number of bytes used to store the double</returns>
public int WriteDouble(double val)
internal int WriteDouble(double val)
{
byteBuffer[0] = 0x08; // length
doubleBuffer[0] = val;
Buffer.BlockCopy(doubleBuffer, 0, byteBuffer, 1, 8);
return WriteHelper(byteBuffer, 9);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 9);
}
/// <summary>
/// Writes a SqlDecimal to the file
/// </summary>
/// <returns>Number of bytes used to store the SqlDecimal</returns>
public int WriteSqlDecimal(SqlDecimal val)
internal int WriteSqlDecimal(SqlDecimal val)
{
int[] arrInt32 = val.Data;
int iLen = 3 + (arrInt32.Length * 4);
@@ -332,7 +350,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
// data value
Buffer.BlockCopy(arrInt32, 0, byteBuffer, 3, iLen - 3);
iTotalLen += WriteHelper(byteBuffer, iLen);
iTotalLen += FileUtils.WriteWithLength(fileStream, byteBuffer, iLen);
return iTotalLen; // len+data
}
@@ -340,7 +358,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// Writes a decimal to the file
/// </summary>
/// <returns>Number of bytes used to store the decimal</returns>
public int WriteDecimal(decimal val)
internal int WriteDecimal(decimal val)
{
int[] arrInt32 = decimal.GetBits(val);
@@ -348,7 +366,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
int iTotalLen = WriteLength(iLen); // length
Buffer.BlockCopy(arrInt32, 0, byteBuffer, 0, iLen);
iTotalLen += WriteHelper(byteBuffer, iLen);
iTotalLen += FileUtils.WriteWithLength(fileStream, byteBuffer, iLen);
return iTotalLen; // len+data
}
@@ -366,7 +384,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// Writes a DateTimeOffset to the file
/// </summary>
/// <returns>Number of bytes used to store the DateTimeOffset</returns>
public int WriteDateTimeOffset(DateTimeOffset dtoVal)
internal int WriteDateTimeOffset(DateTimeOffset dtoVal)
{
// Write the length, which is the 2*sizeof(long)
byteBuffer[0] = 0x10; // length (16)
@@ -376,14 +394,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
longBufferOffset[0] = dtoVal.Ticks;
longBufferOffset[1] = dtoVal.Offset.Ticks;
Buffer.BlockCopy(longBufferOffset, 0, byteBuffer, 1, 16);
return WriteHelper(byteBuffer, 17);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 17);
}
/// <summary>
/// Writes a TimeSpan to the file
/// </summary>
/// <returns>Number of bytes used to store the TimeSpan</returns>
public int WriteTimeSpan(TimeSpan timeSpan)
internal int WriteTimeSpan(TimeSpan timeSpan)
{
return WriteInt64(timeSpan.Ticks);
}
@@ -392,7 +410,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// Writes a string to the file
/// </summary>
/// <returns>Number of bytes used to store the string</returns>
public int WriteString(string sVal)
internal int WriteString(string sVal)
{
Validate.IsNotNull(nameof(sVal), sVal);
@@ -408,7 +426,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
byteBuffer[3] = 0x00;
byteBuffer[4] = 0x00;
iTotalLen = WriteHelper(byteBuffer, 5);
iTotalLen = FileUtils.WriteWithLength(fileStream, byteBuffer, 5);
}
else
{
@@ -417,7 +435,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
// convert char array into byte array and write it out
iTotalLen = WriteLength(bytes.Length);
iTotalLen += WriteHelper(bytes, bytes.Length);
iTotalLen += FileUtils.WriteWithLength(fileStream, bytes, bytes.Length);
}
return iTotalLen; // len+data
}
@@ -426,7 +444,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// Writes a byte[] to the file
/// </summary>
/// <returns>Number of bytes used to store the byte[]</returns>
public int WriteBytes(byte[] bytesVal)
internal int WriteBytes(byte[] bytesVal)
{
Validate.IsNotNull(nameof(bytesVal), bytesVal);
@@ -440,12 +458,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
byteBuffer[3] = 0x00;
byteBuffer[4] = 0x00;
iTotalLen = WriteHelper(byteBuffer, 5);
iTotalLen = FileUtils.WriteWithLength(fileStream, byteBuffer, 5);
}
else
{
iTotalLen = WriteLength(bytesVal.Length);
iTotalLen += WriteHelper(bytesVal, bytesVal.Length);
iTotalLen += FileUtils.WriteWithLength(fileStream, bytesVal, bytesVal.Length);
}
return iTotalLen; // len+data
}
@@ -455,7 +473,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// </summary>
/// <param name="val">The GUID to write to the file</param>
/// <returns>Number of bytes written to the file</returns>
public int WriteGuid(Guid val)
internal int WriteGuid(Guid val)
{
byte[] guidBytes = val.ToByteArray();
return WriteBytes(guidBytes);
@@ -466,23 +484,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// </summary>
/// <param name="val">The SqlMoney value to write to the file</param>
/// <returns>Number of bytes written to the file</returns>
public int WriteMoney(SqlMoney val)
internal int WriteMoney(SqlMoney val)
{
return WriteDecimal(val.Value);
}
/// <summary>
/// Flushes the internal buffer to the file stream
/// </summary>
public void FlushBuffer()
{
fileStream.Flush();
}
#endregion
#region Private Helpers
/// <summary>
/// Creates a new buffer that is of the specified length if the buffer is not already
/// at least as long as specified.
@@ -509,7 +515,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
int iTmp = iLen & 0x000000FF;
byteBuffer[0] = Convert.ToByte(iTmp);
return WriteHelper(byteBuffer, 1);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 1);
}
// The length won't fit in 1 byte, so we need to use 1 byte to signify that the length
// is a full 4 bytes.
@@ -518,7 +524,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
// convert int32 into array of bytes
intBuffer[0] = iLen;
Buffer.BlockCopy(intBuffer, 0, byteBuffer, 1, 4);
return WriteHelper(byteBuffer, 5);
return FileUtils.WriteWithLength(fileStream, byteBuffer, 5);
}
/// <summary>
@@ -534,12 +540,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
return val.IsNull ? WriteNull() : valueWriteFunc(val);
}
private int WriteHelper(byte[] buffer, int length)
{
fileStream.Write(buffer, 0, length);
return length;
}
#endregion
#region IDisposable Implementation