Feature/save results as csv (#33)

* Code changes to save results as csv

*  changes to save resultset as csv

* removed csvHelper

* Retrieve right resultSet after batch execution]

* code clean up

* encode column names and use string.Join

* code review changes - property fix and rowBuilder removal

* changes to fix execution tests

* Code clean up

* Code clean up

* Test save as CSV

* Fix tests for Mac/Linux

* Add doc comment

* Add doc comments

* Delete file if exception occurs
This commit is contained in:
Sharon Ravindran
2016-09-12 17:11:46 -07:00
committed by GitHub
parent 14b6348b20
commit 0bd084d9f1
5 changed files with 429 additions and 1 deletions

View File

@@ -0,0 +1,70 @@
//
// 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 Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
{
/// <summary>
/// Parameters for the save results request
/// </summary>
public class SaveResultsRequestParams
{
/// <summary>
/// The path of the file to save results in
/// </summary>
public string FilePath { get; set; }
/// <summary>
/// The encoding of the file to save results in
/// </summary>
public string FileEncoding { get; set; }
/// <summary>
/// Include headers of columns in CSV
/// </summary>
public bool IncludeHeaders { get; set; }
/// <summary>
/// Index of the batch to get the results from
/// </summary>
public int BatchIndex { get; set; }
/// <summary>
/// Index of the result set to get the results from
/// </summary>
public int ResultSetIndex { get; set; }
/// <summary>
/// CSV - Write values in quotes
/// </summary>
public Boolean ValueInQuotes { get; set; }
/// <summary>
/// URI for the editor that called save results
/// </summary>
public string OwnerUri { get; set; }
}
/// <summary>
/// Parameters for the save results result
/// </summary>
public class SaveResultRequestResult
{
/// <summary>
/// Error messages for saving to file.
/// </summary>
public string Messages { get; set; }
}
public class SaveResultsAsCsvRequest
{
public static readonly
RequestType<SaveResultsRequestParams, SaveResultRequestResult> Type =
RequestType<SaveResultsRequestParams, SaveResultRequestResult>.Create("query/save");
}
}

View File

@@ -2,9 +2,10 @@
// 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.Concurrent;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting;
@@ -98,6 +99,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
serviceHost.SetRequestHandler(QueryExecuteSubsetRequest.Type, HandleResultSubsetRequest);
serviceHost.SetRequestHandler(QueryDisposeRequest.Type, HandleDisposeRequest);
serviceHost.SetRequestHandler(QueryCancelRequest.Type, HandleCancelRequest);
serviceHost.SetRequestHandler(SaveResultsAsCsvRequest.Type, HandleSaveResultsAsCsvRequest);
// Register handler for shutdown event
serviceHost.RegisterShutdownTask((shutdownParams, requestContext) =>
@@ -256,6 +258,58 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
}
/// <summary>
/// Process request to save a resultSet to a file in CSV format
/// </summary>
public async Task HandleSaveResultsAsCsvRequest( SaveResultsRequestParams saveParams,
RequestContext<SaveResultRequestResult> requestContext)
{
// retrieve query for OwnerUri
Query result;
if (!ActiveQueries.TryGetValue(saveParams.OwnerUri, out result))
{
await requestContext.SendResult(new SaveResultRequestResult
{
Messages = "Failed to save results, ID not found."
});
return;
}
try
{
using (StreamWriter csvFile = new StreamWriter(File.OpenWrite(saveParams.FilePath)))
{
// get the requested resultSet from query
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
ResultSet selectedResultSet = (selectedBatch.ResultSets.ToList())[saveParams.ResultSetIndex];
if ( saveParams.IncludeHeaders)
{
// write column names to csv
await csvFile.WriteLineAsync( string.Join( ",", selectedResultSet.Columns.Select( column => SaveResults.EncodeCsvField(column.ColumnName) ?? string.Empty)));
}
// write rows to csv
foreach( var row in selectedResultSet.Rows)
{
await csvFile.WriteLineAsync(string.Join( ",", row.Select( field => SaveResults.EncodeCsvField( (field != null) ? field.ToString(): string.Empty))));
}
}
}
catch(Exception ex)
{
// Delete file when exception occurs
if(File.Exists(saveParams.FilePath))
{
File.Delete(saveParams.FilePath);
}
await requestContext.SendError(ex.Message);
return;
}
await requestContext.SendResult(new SaveResultRequestResult
{
Messages = "Success"
});
return;
}
#endregion
#region Private Helpers

View File

@@ -111,6 +111,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// </summary>
public long RowCount { get; private set; }
/// <summary>
/// The rows of this result set
/// </summary>
public IEnumerable<object[]> Rows
{
get { return FileOffsets.Select(offset => fileStreamReader.ReadRow(offset, Columns)); }
}
#endregion
#region Public Methods

View File

@@ -0,0 +1,84 @@
//
// 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.Text;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
internal class SaveResults{
/// Method ported from SSMS
/// <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)
{
StringBuilder sbField = new StringBuilder(field);
//Whether this field has special characters which require it to be embedded in quotes
bool embedInQuotes = false;
//Check for leading/trailing spaces
if (sbField.Length > 0 &&
(sbField[0] == ' ' ||
sbField[0] == '\t' ||
sbField[sbField.Length - 1] == ' ' ||
sbField[sbField.Length - 1] == '\t'))
{
embedInQuotes = true;
}
else
{ //List separator being in the field will require quotes
if (field.Contains(","))
{
embedInQuotes = true;
}
else
{
for (int i = 0; i < sbField.Length; ++i)
{
//Check whether this character is a special character
if (sbField[i] == '\r' ||
sbField[i] == '\n' ||
sbField[i] == '"')
{ //If even one character requires embedding the whole field will
//be embedded in quotes so we can just break out now
embedInQuotes = true;
break;
}
}
}
}
//Replace all quotes in the original field with double quotes
sbField.Replace("\"", "\"\"");
String ret = sbField.ToString();
if (embedInQuotes)
{
ret = "\"" + ret + "\"";
}
return ret;
}
}
}