mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-15 09:35:37 -05:00
Export headers in an empty result set (#1434)
* Minimal changes to make headers appear on empty result sets * Columns for everyone! * Updating tests - some don't pass yet * Adding some more tests to verify the changes for column/row selection * null default columns * Updates to comments as per PR comments
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
@@ -14,7 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
IFileStreamReader GetReader(string fileName);
|
||||
|
||||
IFileStreamWriter GetWriter(string fileName);
|
||||
IFileStreamWriter GetWriter(string fileName, IReadOnlyList<DbColumnWrapper> columns = null);
|
||||
|
||||
void DisposeFile(string fileName);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
public interface IFileStreamWriter : IDisposable
|
||||
{
|
||||
int WriteRow(StorageDataReader dataReader);
|
||||
void WriteRow(IList<DbCellValue> row, IList<DbColumnWrapper> columns);
|
||||
void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns);
|
||||
void Seek(long offset);
|
||||
void FlushBuffer();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,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.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
@@ -48,17 +49,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>Stream reader</returns>
|
||||
public IFileStreamReader GetReader(string fileName)
|
||||
{
|
||||
return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), QueryExecutionSettings);
|
||||
return new ServiceBufferFileStreamReader(
|
||||
new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
|
||||
QueryExecutionSettings
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new CSV writer for writing results to a CSV file, file share is ReadWrite to allow concurrent reads/writes to the file.
|
||||
/// </summary>
|
||||
/// <param name="fileName">Path to the CSV output 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>
|
||||
/// <returns>Stream writer</returns>
|
||||
public IFileStreamWriter GetWriter(string fileName)
|
||||
public IFileStreamWriter GetWriter(string fileName, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
return new SaveAsCsvFileStreamWriter(new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite), SaveRequestParams);
|
||||
return new SaveAsCsvFileStreamWriter(
|
||||
new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite),
|
||||
SaveRequestParams,
|
||||
columns
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
@@ -20,21 +20,74 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
#region Member Variables
|
||||
|
||||
private readonly SaveResultsAsCsvRequestParams saveParams;
|
||||
private bool headerWritten;
|
||||
private readonly char delimiter;
|
||||
private readonly Encoding encoding;
|
||||
private readonly string lineSeparator;
|
||||
private readonly char textIdentifier;
|
||||
private readonly string textIdentifierString;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor, stores the CSV specific request params locally, chains into the base
|
||||
/// 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)
|
||||
/// <param name="columns">
|
||||
/// The entire list of columns for the result set. They will be filtered down as per the
|
||||
/// request params.
|
||||
/// </param>
|
||||
public SaveAsCsvFileStreamWriter(Stream stream, SaveResultsAsCsvRequestParams requestParams, IReadOnlyList<DbColumnWrapper> columns)
|
||||
: base(stream, requestParams, columns)
|
||||
{
|
||||
saveParams = requestParams;
|
||||
// Parse the config
|
||||
delimiter = ',';
|
||||
if (!string.IsNullOrEmpty(requestParams.Delimiter))
|
||||
{
|
||||
delimiter = requestParams.Delimiter[0];
|
||||
}
|
||||
|
||||
lineSeparator = Environment.NewLine;
|
||||
if (!string.IsNullOrEmpty(requestParams.LineSeperator))
|
||||
{
|
||||
lineSeparator = requestParams.LineSeperator;
|
||||
}
|
||||
|
||||
textIdentifier = '"';
|
||||
if (!string.IsNullOrEmpty(requestParams.TextIdentifier))
|
||||
{
|
||||
textIdentifier = requestParams.TextIdentifier[0];
|
||||
}
|
||||
textIdentifierString = textIdentifier.ToString();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
try
|
||||
{
|
||||
encoding = int.TryParse(requestParams.Encoding, out int codePage)
|
||||
? Encoding.GetEncoding(codePage)
|
||||
: Encoding.GetEncoding(requestParams.Encoding);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback encoding when specified codepage is invalid
|
||||
encoding = Encoding.GetEncoding("utf-8");
|
||||
}
|
||||
|
||||
// Output the header if the user requested it
|
||||
if (requestParams.IncludeHeaders)
|
||||
{
|
||||
// Build the string
|
||||
var selectedColumns = columns.Skip(ColumnStartIndex)
|
||||
.Take(ColumnCount)
|
||||
.Select(c => EncodeCsvField(c.ColumnName) ?? string.Empty);
|
||||
|
||||
string headerLine = string.Join(delimiter, selectedColumns);
|
||||
|
||||
// Encode it and write it out
|
||||
byte[] headerBytes = encoding.GetBytes(headerLine + lineSeparator);
|
||||
FileStream.Write(headerBytes, 0, headerBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,76 +95,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// 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)
|
||||
/// <param name="columns">The columns for the row to output</param>
|
||||
public override void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
char delimiter = ',';
|
||||
if(!string.IsNullOrEmpty(saveParams.Delimiter))
|
||||
{
|
||||
// first char in string
|
||||
delimiter = saveParams.Delimiter[0];
|
||||
}
|
||||
|
||||
string lineSeperator = Environment.NewLine;
|
||||
if(!string.IsNullOrEmpty(saveParams.LineSeperator))
|
||||
{
|
||||
lineSeperator = saveParams.LineSeperator;
|
||||
}
|
||||
|
||||
char textIdentifier = '"';
|
||||
if(!string.IsNullOrEmpty(saveParams.TextIdentifier))
|
||||
{
|
||||
// first char in string
|
||||
textIdentifier = saveParams.TextIdentifier[0];
|
||||
}
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
int codepage;
|
||||
Encoding encoding;
|
||||
try
|
||||
{
|
||||
if(int.TryParse(saveParams.Encoding, out codepage))
|
||||
{
|
||||
encoding = Encoding.GetEncoding(codepage);
|
||||
}
|
||||
else
|
||||
{
|
||||
encoding = Encoding.GetEncoding(saveParams.Encoding);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback encoding when specified codepage is invalid
|
||||
encoding = Encoding.GetEncoding("utf-8");
|
||||
}
|
||||
|
||||
// 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, delimiter, textIdentifier) ?? string.Empty);
|
||||
|
||||
string headerLine = string.Join(delimiter, selectedColumns);
|
||||
|
||||
// Encode it and write it out
|
||||
byte[] headerBytes = encoding.GetBytes(headerLine + lineSeperator);
|
||||
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, delimiter, textIdentifier));
|
||||
var selectedCells = row.Skip(ColumnStartIndex)
|
||||
.Take(ColumnCount)
|
||||
.Select(c => EncodeCsvField(c.DisplayValue));
|
||||
string rowLine = string.Join(delimiter, selectedCells);
|
||||
|
||||
// Encode it and write it out
|
||||
byte[] rowBytes = encoding.GetBytes(rowLine + lineSeperator);
|
||||
byte[] rowBytes = encoding.GetBytes(rowLine + lineSeparator);
|
||||
FileStream.Write(rowBytes, 0, rowBytes.Length);
|
||||
}
|
||||
|
||||
@@ -124,7 +118,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <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 delimiter 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>
|
||||
@@ -132,27 +126,24 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </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, char delimiter, char textIdentifier)
|
||||
internal string EncodeCsvField(string field)
|
||||
{
|
||||
string strTextIdentifier = textIdentifier.ToString();
|
||||
|
||||
// Special case for nulls
|
||||
if (field == null)
|
||||
{
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
// Replace all quotes in the original field with double quotes
|
||||
string ret = field.Replace(textIdentifierString, textIdentifierString + textIdentifierString);
|
||||
|
||||
// Whether this field has special characters which require it to be embedded in quotes
|
||||
bool embedInQuotes = field.IndexOfAny(new[] { delimiter, '\r', '\n', textIdentifier }) >= 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(strTextIdentifier, strTextIdentifier + strTextIdentifier);
|
||||
|
||||
if (embedInQuotes)
|
||||
{
|
||||
ret = strTextIdentifier + $"{ret}" + strTextIdentifier;
|
||||
ret = $"{textIdentifier}{ret}{textIdentifier}";
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -1,9 +1,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.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
@@ -48,17 +49,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>Stream reader</returns>
|
||||
public IFileStreamReader GetReader(string fileName)
|
||||
{
|
||||
return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), QueryExecutionSettings);
|
||||
return new ServiceBufferFileStreamReader(
|
||||
new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
|
||||
QueryExecutionSettings
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new Excel writer for writing results to a Excel file, file share is ReadWrite to allow concurrent reads/writes to the file.
|
||||
/// </summary>
|
||||
/// <param name="fileName">Path to the Excel output 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>
|
||||
/// <returns>Stream writer</returns>
|
||||
public IFileStreamWriter GetWriter(string fileName)
|
||||
public IFileStreamWriter GetWriter(string fileName, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
return new SaveAsExcelFileStreamWriter(new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite), SaveRequestParams);
|
||||
return new SaveAsExcelFileStreamWriter(
|
||||
new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite),
|
||||
SaveRequestParams,
|
||||
columns
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
@@ -25,13 +25,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor, stores the Excel specific request params locally, chains into the base
|
||||
/// Constructor, stores the Excel specific request params locally, chains into the base
|
||||
/// constructor
|
||||
/// </summary>
|
||||
/// <param name="stream">FileStream to access the Excel file output</param>
|
||||
/// <param name="requestParams">Excel save as request parameters</param>
|
||||
public SaveAsExcelFileStreamWriter(Stream stream, SaveResultsAsExcelRequestParams requestParams)
|
||||
: base(stream, requestParams)
|
||||
/// <param name="columns">
|
||||
/// The entire list of columns for the result set. They will be filtered down as per the
|
||||
/// request params.
|
||||
/// </param>
|
||||
public SaveAsExcelFileStreamWriter(Stream stream, SaveResultsAsExcelRequestParams requestParams, IReadOnlyList<DbColumnWrapper> columns)
|
||||
: base(stream, requestParams, columns)
|
||||
{
|
||||
saveParams = requestParams;
|
||||
helper = new SaveAsExcelFileStreamWriterHelper(stream);
|
||||
@@ -47,16 +51,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// 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)
|
||||
public override void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
int columnStart = ColumnStartIndex ?? 0;
|
||||
int columnEnd = (ColumnEndIndex != null) ? ColumnEndIndex.Value + 1 : columns.Count;
|
||||
|
||||
// Write out the header if we haven't already and the user chose to have it
|
||||
if (saveParams.IncludeHeaders && !headerWritten)
|
||||
{
|
||||
sheet.AddRow();
|
||||
for (int i = columnStart; i < columnEnd; i++)
|
||||
for (int i = ColumnStartIndex; i <= ColumnEndIndex; i++)
|
||||
{
|
||||
sheet.AddCell(columns[i].ColumnName);
|
||||
}
|
||||
@@ -64,7 +65,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
}
|
||||
|
||||
sheet.AddRow();
|
||||
for (int i = columnStart; i < columnEnd; i++)
|
||||
for (int i = ColumnStartIndex; i <= ColumnEndIndex; i++)
|
||||
{
|
||||
sheet.AddCell(row[i]);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,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.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
@@ -52,10 +53,18 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// Returns a new JSON writer for writing results to a JSON file, file share is ReadWrite to allow concurrent reads/writes to the file.
|
||||
/// </summary>
|
||||
/// <param name="fileName">Path to the JSON output 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>
|
||||
/// <returns>Stream writer</returns>
|
||||
public IFileStreamWriter GetWriter(string fileName)
|
||||
public IFileStreamWriter GetWriter(string fileName, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
return new SaveAsJsonFileStreamWriter(new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite), SaveRequestParams);
|
||||
return new SaveAsJsonFileStreamWriter(
|
||||
new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite),
|
||||
SaveRequestParams,
|
||||
columns
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
@@ -33,8 +33,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </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)
|
||||
/// <param name="columns">
|
||||
/// The entire list of columns for the result set. They will be filtered down as per the
|
||||
/// request params.
|
||||
/// </param>
|
||||
public SaveAsJsonFileStreamWriter(Stream stream, SaveResultsRequestParams requestParams, IReadOnlyList<DbColumnWrapper> columns)
|
||||
: base(stream, requestParams, columns)
|
||||
{
|
||||
// Setup the internal state
|
||||
streamWriter = new StreamWriter(stream);
|
||||
@@ -53,15 +57,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// 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)
|
||||
public override void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
// Write the header for the object
|
||||
jsonWriter.WriteStartObject();
|
||||
|
||||
// Write the items out as properties
|
||||
int columnStart = ColumnStartIndex ?? 0;
|
||||
int columnEnd = (ColumnEndIndex != null) ? ColumnEndIndex.Value + 1 : columns.Count;
|
||||
for (int i = columnStart; i < columnEnd; i++)
|
||||
for (int i = ColumnStartIndex; i <= ColumnEndIndex; i++)
|
||||
{
|
||||
jsonWriter.WritePropertyName(columns[i].ColumnName);
|
||||
if (row[i].RawObject == null)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
@@ -21,18 +22,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </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)
|
||||
/// <param name="columns">
|
||||
/// The entire list of columns for the result set. Used to determine which columns to
|
||||
/// output.
|
||||
/// </param>
|
||||
protected SaveAsStreamWriter(Stream stream, SaveResultsRequestParams requestParams, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
Validate.IsNotNull(nameof(stream), stream);
|
||||
Validate.IsNotNull(nameof(columns), columns);
|
||||
|
||||
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;
|
||||
ColumnStartIndex = requestParams.ColumnStartIndex.Value;
|
||||
ColumnEndIndex = requestParams.ColumnEndIndex.Value;
|
||||
// ReSharper restore PossibleInvalidOperationException
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save request was for the entire result set, use default start/end
|
||||
ColumnStartIndex = 0;
|
||||
ColumnEndIndex = columns.Count - 1;
|
||||
}
|
||||
|
||||
ColumnCount = ColumnEndIndex - ColumnStartIndex + 1;
|
||||
}
|
||||
|
||||
#region Properties
|
||||
@@ -40,22 +54,22 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <summary>
|
||||
/// Index of the first column to write to the output file
|
||||
/// </summary>
|
||||
protected int? ColumnStartIndex { get; private set; }
|
||||
protected int ColumnStartIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of columns to write to the output file
|
||||
/// </summary>
|
||||
protected int? ColumnCount { get; private set; }
|
||||
protected int ColumnCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Index of the last column to write to the output file
|
||||
/// Index of the last column to write to the output file (inclusive).
|
||||
/// </summary>
|
||||
protected int? ColumnEndIndex { get; private set; }
|
||||
protected int ColumnEndIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The file stream to use to write the output file
|
||||
/// </summary>
|
||||
protected Stream FileStream { get; private set; }
|
||||
protected Stream FileStream { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -73,7 +87,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </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);
|
||||
public abstract void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns);
|
||||
|
||||
/// <summary>
|
||||
/// Not implemented, do not use.
|
||||
|
||||
@@ -1,9 +1,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.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
@@ -45,17 +46,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>Stream reader</returns>
|
||||
public IFileStreamReader GetReader(string fileName)
|
||||
{
|
||||
return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), QueryExecutionSettings);
|
||||
return new ServiceBufferFileStreamReader(
|
||||
new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
|
||||
QueryExecutionSettings
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new XML writer for writing results to a XML file, file share is ReadWrite to allow concurrent reads/writes to the file.
|
||||
/// </summary>
|
||||
/// <param name="fileName">Path to the XML output 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>
|
||||
/// <returns>Stream writer</returns>
|
||||
public IFileStreamWriter GetWriter(string fileName)
|
||||
public IFileStreamWriter GetWriter(string fileName, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
return new SaveAsXmlFileStreamWriter(new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite), SaveRequestParams);
|
||||
return new SaveAsXmlFileStreamWriter(
|
||||
new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite),
|
||||
SaveRequestParams,
|
||||
columns
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
@@ -24,10 +24,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
// Root element name for the output XML
|
||||
private const string RootElementTag = "data";
|
||||
|
||||
|
||||
// Item element name which will be used for every row
|
||||
private const string ItemElementTag = "row";
|
||||
|
||||
|
||||
#region Member Variables
|
||||
|
||||
private readonly XmlTextWriter xmlTextWriter;
|
||||
@@ -39,8 +39,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// </summary>
|
||||
/// <param name="stream">FileStream to access the JSON file output</param>
|
||||
/// <param name="requestParams">XML save as request parameters</param>
|
||||
public SaveAsXmlFileStreamWriter(Stream stream, SaveResultsAsXmlRequestParams requestParams)
|
||||
: base(stream, requestParams)
|
||||
/// <param name="columns">
|
||||
/// The entire list of columns for the result set. They will be filtered down as per the
|
||||
/// request params.
|
||||
/// </param>
|
||||
public SaveAsXmlFileStreamWriter(Stream stream, SaveResultsAsXmlRequestParams requestParams, IReadOnlyList<DbColumnWrapper> columns)
|
||||
: base(stream, requestParams, columns)
|
||||
{
|
||||
// Setup the internal state
|
||||
var encoding = GetEncoding(requestParams);
|
||||
@@ -60,19 +64,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// 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)
|
||||
public override void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
// Write the header for the object
|
||||
xmlTextWriter.WriteStartElement(ItemElementTag);
|
||||
|
||||
// Write the items out as properties
|
||||
int columnStart = ColumnStartIndex ?? 0;
|
||||
int columnEnd = ColumnEndIndex + 1 ?? columns.Count;
|
||||
for (int i = columnStart; i < columnEnd; i++)
|
||||
for (int i = ColumnStartIndex; i <= ColumnEndIndex; i++)
|
||||
{
|
||||
// Write the column name as item tag
|
||||
xmlTextWriter.WriteStartElement(columns[i].ColumnName);
|
||||
|
||||
|
||||
if (row[i].RawObject != null)
|
||||
{
|
||||
xmlTextWriter.WriteString(row[i].DisplayValue);
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
@@ -40,7 +42,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>A <see cref="ServiceBufferFileStreamReader"/></returns>
|
||||
public IFileStreamReader GetReader(string fileName)
|
||||
{
|
||||
return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), QueryExecutionSettings);
|
||||
return new ServiceBufferFileStreamReader(
|
||||
new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
|
||||
QueryExecutionSettings
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,10 +53,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// SSMS formatted buffer file, file share is ReadWrite to allow concurrent reads/writes to the file.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The file to write values to</param>
|
||||
/// <param name="columns">
|
||||
/// Ignored in order to fulfil the <see cref="IFileStreamFactory"/> contract.
|
||||
/// @TODO: Refactor this out so that save-as writers do not use the same contract as service buffer writers.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="ServiceBufferFileStreamWriter"/></returns>
|
||||
public IFileStreamWriter GetWriter(string fileName)
|
||||
public IFileStreamWriter GetWriter(string fileName, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
return new ServiceBufferFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite), QueryExecutionSettings);
|
||||
return new ServiceBufferFileStreamWriter(
|
||||
new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite),
|
||||
QueryExecutionSettings
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
}
|
||||
else
|
||||
{
|
||||
// not a long field
|
||||
// not a long field
|
||||
values[i] = reader.GetValue(i);
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public void WriteRow(IList<DbCellValue> row, IList<DbColumnWrapper> columns)
|
||||
public void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns)
|
||||
{
|
||||
throw new InvalidOperationException("This type of writer is meant to write values from a DbDataReader only.");
|
||||
}
|
||||
@@ -442,7 +442,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
// Convert to a unicode byte array
|
||||
byte[] bytes = Encoding.Unicode.GetBytes(sVal);
|
||||
|
||||
// convert char array into byte array and write it out
|
||||
// convert char array into byte array and write it out
|
||||
iTotalLen = WriteLength(bytes.Length);
|
||||
iTotalLen += FileUtilities.WriteWithLength(fileStream, bytes, bytes.Length);
|
||||
}
|
||||
|
||||
@@ -306,12 +306,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the execution plan from the table returned
|
||||
/// Generates the execution plan from the table returned
|
||||
/// </summary>
|
||||
/// <returns>An execution plan object</returns>
|
||||
public Task<Contracts.ExecutionPlan> GetExecutionPlan()
|
||||
{
|
||||
// Process the action just in case it hasn't been yet
|
||||
// Process the action just in case it hasn't been yet
|
||||
ProcessSpecialAction();
|
||||
|
||||
// Sanity check to make sure that results read has started
|
||||
@@ -319,7 +319,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceResultSetNotRead);
|
||||
}
|
||||
// Check that we this result set contains a showplan
|
||||
// Check that we this result set contains a showplan
|
||||
if (!specialAction.ExpectYukonXMLShowPlan)
|
||||
{
|
||||
throw new Exception(SR.QueryServiceExecutionPlanNotFound);
|
||||
@@ -327,7 +327,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
{
|
||||
string content;
|
||||
string format = null;
|
||||
|
||||
@@ -336,12 +336,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// Determine the format and get the first col/row of XML
|
||||
content = fileStreamReader.ReadRow(0, 0, Columns)[0].DisplayValue;
|
||||
|
||||
if (specialAction.ExpectYukonXMLShowPlan)
|
||||
if (specialAction.ExpectYukonXMLShowPlan)
|
||||
{
|
||||
format = "xml";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new Contracts.ExecutionPlan
|
||||
{
|
||||
Format = format,
|
||||
@@ -371,7 +371,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
// Open a writer for the file
|
||||
//
|
||||
var fileWriter = fileStreamFactory.GetWriter(outputFileName);
|
||||
var fileWriter = fileStreamFactory.GetWriter(outputFileName, null);
|
||||
using (fileWriter)
|
||||
{
|
||||
// If we can initialize the columns using the column schema, use that
|
||||
@@ -456,7 +456,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the values in a row with the
|
||||
/// Updates the values in a row with the
|
||||
/// </summary>
|
||||
/// <param name="rowId"></param>
|
||||
/// <param name="dbDataReader"></param>
|
||||
@@ -528,7 +528,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
|
||||
using (var fileReader = fileFactory.GetReader(outputFileName))
|
||||
using (var fileWriter = fileFactory.GetWriter(saveParams.FilePath))
|
||||
using (var fileWriter = fileFactory.GetWriter(saveParams.FilePath, Columns))
|
||||
{
|
||||
// Iterate over the rows that are in the selected row set
|
||||
for (long i = rowStartIndex; i < rowEndIndex; ++i)
|
||||
@@ -551,13 +551,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Add exception handling to the save task
|
||||
Task taskWithHandling = saveAsTask.ContinueWithOnFaulted(async t =>
|
||||
{
|
||||
if (failureHandler != null)
|
||||
{
|
||||
await failureHandler(saveParams, t.Exception.Message);
|
||||
await failureHandler(saveParams, t.Exception?.Message);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -691,7 +691,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
{
|
||||
// Release the sendResultsSemphore so the next invocation gets unblocked
|
||||
//
|
||||
sendResultsSemphore.Release();
|
||||
@@ -706,7 +706,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
/// <summary>
|
||||
/// If the result set represented by this class corresponds to a single XML
|
||||
/// column that contains results of "for xml" query, set isXml = true
|
||||
/// column that contains results of "for xml" query, set isXml = true
|
||||
/// If the result set represented by this class corresponds to a single JSON
|
||||
/// column that contains results of "for json" query, set isJson = true
|
||||
/// </summary>
|
||||
@@ -755,10 +755,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <summary>
|
||||
/// Determine the special action, if any, for this result set
|
||||
/// </summary>
|
||||
private SpecialAction ProcessSpecialAction()
|
||||
{
|
||||
private SpecialAction ProcessSpecialAction()
|
||||
{
|
||||
|
||||
// Check if this result set is a showplan
|
||||
// Check if this result set is a showplan
|
||||
if (Columns.Length == 1 && string.Compare(Columns[0].ColumnName, YukonXmlShowPlanColumn, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
specialAction.ExpectYukonXMLShowPlan = true;
|
||||
@@ -780,7 +780,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceResultSetNotRead);
|
||||
}
|
||||
// NOTE: We are no longer checking to see if the data reader has rows before reading
|
||||
// NOTE: We are no longer checking to see if the data reader has rows before reading
|
||||
// b/c of a quirk in SqlClient. In some scenarios, a SqlException isn't thrown until we
|
||||
// read. In order to get appropriate errors back to the user, we'll read first.
|
||||
// Returning false from .ReadAsync means there aren't any rows.
|
||||
@@ -791,7 +791,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceResultSetAddNoRows);
|
||||
}
|
||||
|
||||
|
||||
using (IFileStreamWriter writer = fileStreamFactory.GetWriter(outputFileName))
|
||||
{
|
||||
// Write the row to the end of the file
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
inProgressSerializations.AddOrUpdate(serializer.FilePath, serializer, (key, old) => serializer);
|
||||
}
|
||||
|
||||
|
||||
Logger.Write(TraceEventType.Verbose, "HandleSerializeStartRequest");
|
||||
SerializeDataResult result = serializer.ProcessRequest(serializeParams);
|
||||
await requestContext.SendResult(result);
|
||||
@@ -153,7 +153,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
private IFileStreamWriter writer;
|
||||
private SerializeDataStartRequestParams requestParams;
|
||||
private IList<DbColumnWrapper> columns;
|
||||
private IReadOnlyList<DbColumnWrapper> columns;
|
||||
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
this.FilePath = requestParams.FilePath;
|
||||
}
|
||||
|
||||
private IList<DbColumnWrapper> MapColumns(ColumnInfo[] columns)
|
||||
private IReadOnlyList<DbColumnWrapper> MapColumns(ColumnInfo[] columns)
|
||||
{
|
||||
List<DbColumnWrapper> columnWrappers = new List<DbColumnWrapper>();
|
||||
foreach (ColumnInfo column in columns)
|
||||
@@ -258,7 +258,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
default:
|
||||
throw new Exception(SR.SerializationServiceUnsupportedFormat(this.requestParams.SaveFormat));
|
||||
}
|
||||
this.writer = factory.GetWriter(requestParams.FilePath);
|
||||
this.writer = factory.GetWriter(requestParams.FilePath, columns);
|
||||
}
|
||||
}
|
||||
public void CloseStreams()
|
||||
|
||||
Reference in New Issue
Block a user