mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-25 09:35:37 -05:00
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:
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user