mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -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:
@@ -1,11 +1,11 @@
|
||||
//
|
||||
//
|
||||
// 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 System.Text.RegularExpressions;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
@@ -18,144 +18,247 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
public class SaveAsCsvFileStreamWriterTests
|
||||
{
|
||||
[Test]
|
||||
public void EncodeCsvFieldShouldWrap(
|
||||
[Values("Something\rElse",
|
||||
"Something\nElse",
|
||||
"Something\"Else",
|
||||
"Something,Else",
|
||||
"\tSomething",
|
||||
"Something\t",
|
||||
" Something",
|
||||
"Something ",
|
||||
" \t\r\n\",\r\n\"\r ")] string field)
|
||||
public void Constructor_NullStream()
|
||||
{
|
||||
// If: I CSV encode a field that has forbidden characters in it
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField(field, ',', '\"');
|
||||
// Act
|
||||
TestDelegate action = () => _ = new SaveAsCsvFileStreamWriter(
|
||||
null,
|
||||
new SaveResultsAsCsvRequestParams(),
|
||||
Array.Empty<DbColumnWrapper>()
|
||||
);
|
||||
|
||||
// Then: It should wrap it in quotes
|
||||
Assert.True(Regex.IsMatch(output, "^\".*")
|
||||
&& Regex.IsMatch(output, ".*\"$"));
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(action);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EncodeCsvFieldShouldNotWrap(
|
||||
[Values(
|
||||
"Something",
|
||||
"Something valid.",
|
||||
"Something\tvalid"
|
||||
)] string field)
|
||||
public void Constructor_NullColumns()
|
||||
{
|
||||
// Act
|
||||
TestDelegate action = () => _ = new SaveAsCsvFileStreamWriter(
|
||||
Stream.Null,
|
||||
new SaveResultsAsCsvRequestParams(),
|
||||
null
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(action);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithoutSelectionWithHeader_WritesHeaderWithAllColumns()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made, headers should be printed
|
||||
// ... Create a set of columns
|
||||
// --- Create a memory location to store the output
|
||||
var requestParams = new SaveResultsAsCsvRequestParams { IncludeHeaders = true };
|
||||
var (columns, _) = GetTestValues(2);
|
||||
using var outputStream = new MemoryStream();
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I construct a CSV file writer
|
||||
using var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
||||
writer.Dispose();
|
||||
|
||||
// Then:
|
||||
// ... It should have written a line
|
||||
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
||||
Assert.AreEqual(1, lines.Length);
|
||||
|
||||
// ... It should have written a header line with two comma separated names
|
||||
string[] headerValues = lines[0].Split(",");
|
||||
Assert.AreEqual(2, headerValues.Length);
|
||||
for (int i = 0; i < columns.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(columns[i].ColumnName, headerValues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithSelectionWithHeader_WritesHeaderWithSelectedColumns()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made, headers should be printed
|
||||
// ... Create a set of columns
|
||||
// --- Create a memory location to store the output
|
||||
var requestParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
IncludeHeaders = true,
|
||||
ColumnStartIndex = 1,
|
||||
ColumnEndIndex = 2,
|
||||
RowStartIndex = 0, // Including b/c it is required to be a "save selection"
|
||||
RowEndIndex = 10
|
||||
};
|
||||
var (columns, _) = GetTestValues(4);
|
||||
using var outputStream = new MemoryStream();
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I construct a CSV file writer
|
||||
using var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
||||
writer.Dispose();
|
||||
|
||||
// Then:
|
||||
// ... It should have written a line
|
||||
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
||||
Assert.AreEqual(1, lines.Length);
|
||||
|
||||
// ... It should have written a header line with two comma separated names
|
||||
string[] headerValues = lines[0].Split(",");
|
||||
Assert.AreEqual(2, headerValues.Length);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Assert.AreEqual(columns[i + 1].ColumnName, headerValues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constructor_WithoutSelectionWithoutHeader_DoesNotWriteHeader()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made, headers should not be printed
|
||||
// ... Create a set of columns
|
||||
// --- Create a memory location to store the output
|
||||
var requestParams = new SaveResultsAsCsvRequestParams { IncludeHeaders = false };
|
||||
var (columns, _) = GetTestValues(2);
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I construct a CSV file writer
|
||||
using var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
||||
writer.Dispose();
|
||||
|
||||
// Then:
|
||||
// ... It not have written anything
|
||||
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
||||
Assert.IsEmpty(lines);
|
||||
}
|
||||
|
||||
[TestCase("Something\rElse")] // Contains carriage return
|
||||
[TestCase("Something\nElse")] // Contains line feed
|
||||
[TestCase("Something\"Else")] // Contains default text identifier
|
||||
[TestCase("Something,Else")] // Contains field separator
|
||||
public void EncodeCsvField_ContainsDefaultControlCharacters_ShouldBeWrapped(string field)
|
||||
{
|
||||
// Setup: Create CsvFileStreamWriter using default control characters
|
||||
using var writer = GetWriterForEncodingTests(null, null, null);
|
||||
|
||||
// If: I CSV encode a field that has forbidden characters in it
|
||||
string output = writer.EncodeCsvField(field);
|
||||
|
||||
// Then: It should wrap it in quotes
|
||||
Assert.True(Regex.IsMatch(output, "^\".*\"$", RegexOptions.Singleline));
|
||||
}
|
||||
|
||||
[TestCase("Something\rElse")] // Contains carriage return [TODO: Don't support this]
|
||||
[TestCase("Something\nElse")] // Contains line feed [TODO: Don't support this]
|
||||
[TestCase("Something[Else")] // Contains default text identifier
|
||||
[TestCase("Something$Else")] // Contains field separator
|
||||
//[TestCase("Something||Else")] // Contains line break [TODO: Support this]
|
||||
public void EncodeCsvField_ContainsNonDefaultControlCharacters_ShouldBeWrapped(string field)
|
||||
{
|
||||
// Setup: Create CsvFileStreamWriter using non-default control characters
|
||||
var writer = GetWriterForEncodingTests("$foo", "[bar", "||");
|
||||
|
||||
// If: I CSV encode a field that has forbidden characters in it
|
||||
string output = writer.EncodeCsvField(field);
|
||||
|
||||
// Then: It should wrap it in quotes
|
||||
Assert.True(Regex.IsMatch(output, @"^\[.*\[$", RegexOptions.Singleline));
|
||||
}
|
||||
|
||||
[TestCase("\tSomething")] // Starts with tab
|
||||
[TestCase("Something\t")] // Ends with tab
|
||||
[TestCase("\rSomething")] // Starts with carriage return
|
||||
[TestCase("Something\r")] // Ends with carriage return
|
||||
[TestCase("\nSomething")] // Starts with line feed
|
||||
[TestCase("Something\n")] // Ends with line feed
|
||||
[TestCase(" Something")] // Starts with space
|
||||
[TestCase("Something ")] // Ends with space
|
||||
[TestCase(" Something ")] // Starts and ends with space
|
||||
public void EncodeCsvField_WhitespaceAtFrontOrBack_ShouldBeWrapped(string field)
|
||||
{
|
||||
// Setup: Create CsvFileStreamWriter that specifies the text identifier and field separator
|
||||
var writer = GetWriterForEncodingTests(null, null, null);
|
||||
|
||||
// If: I CSV encode a field that has forbidden characters in it
|
||||
string output = writer.EncodeCsvField(field);
|
||||
|
||||
// Then: It should wrap it in quotes
|
||||
Assert.True(Regex.IsMatch(output, "^\".*\"$", RegexOptions.Singleline));
|
||||
}
|
||||
|
||||
[TestCase("Something")]
|
||||
[TestCase("Something valid.")]
|
||||
[TestCase("Something\tvalid")]
|
||||
public void EncodeCsvField_ShouldNotWrap(string field)
|
||||
{
|
||||
// Setup: Create CsvFileStreamWriter that specifies the text identifier and field separator
|
||||
var writer = GetWriterForEncodingTests(null, null, null);
|
||||
|
||||
// If: I CSV encode a field that does not have forbidden characters in it
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField(field, ',', '\"');
|
||||
string output = writer.EncodeCsvField(field);
|
||||
|
||||
// Then: It should not wrap it in quotes
|
||||
Assert.False(Regex.IsMatch(output, "^\".*\"$"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EncodeCsvFieldReplace()
|
||||
[TestCase(null, "Some\"thing", "\"Some\"\"thing\"")] // Default identifier
|
||||
[TestCase("|$", "Some|thing", "|Some||thing|")] // Custom identifier
|
||||
public void EncodeCsvField_ContainsTextIdentifier_DoublesIdentifierAndWraps(
|
||||
string configuredIdentifier,
|
||||
string input,
|
||||
string expectedOutput)
|
||||
{
|
||||
// Setup: Create CsvFileStreamWriter that specifies the text identifier and field separator
|
||||
var writer = GetWriterForEncodingTests(null, configuredIdentifier, null);
|
||||
|
||||
// If: I CSV encode a field that has a double quote in it,
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField("Some\"thing", ',', '\"');
|
||||
string output = writer.EncodeCsvField(input);
|
||||
|
||||
// Then: It should be replaced with double double quotes
|
||||
Assert.AreEqual("\"Some\"\"thing\"", output);
|
||||
Assert.AreEqual(expectedOutput, output);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EncodeCsvFieldNull()
|
||||
public void EncodeCsvField_Null()
|
||||
{
|
||||
// Setup: Create CsvFileStreamWriter
|
||||
var writer = GetWriterForEncodingTests(null, null, null);
|
||||
|
||||
// If: I CSV encode a null
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField(null, ',', '\"');
|
||||
string output = writer.EncodeCsvField(null);
|
||||
|
||||
// Then: there should be a string version of null returned
|
||||
Assert.AreEqual("NULL", output);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRowWithoutColumnSelectionOrHeader()
|
||||
public void WriteRow_WithoutColumnSelection()
|
||||
{
|
||||
// Setup:
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams();
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
var (columns, data) = GetTestValues(2);
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then: It should write one line with 2 items, comma delimited
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
string[] lines = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
||||
Assert.AreEqual(1, lines.Length);
|
||||
|
||||
string[] values = lines[0].Split(',');
|
||||
Assert.AreEqual(2, values.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRowWithHeader()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made, headers should be printed
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
IncludeHeaders = true
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have written two lines
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
string[] lines = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
Assert.AreEqual(2, lines.Length);
|
||||
|
||||
// ... It should have written a header line with two, comma separated names
|
||||
string[] headerValues = lines[0].Split(',');
|
||||
Assert.AreEqual(2, headerValues.Length);
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
Assert.AreEqual(columns[i].ColumnName, headerValues[i]);
|
||||
}
|
||||
|
||||
// Note: No need to check values, it is done as part of the previous test
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRowWithColumnSelection()
|
||||
public void WriteRow_WithColumnSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that selects n-1 columns from the front and back
|
||||
@@ -166,48 +269,25 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
ColumnStartIndex = 1,
|
||||
ColumnEndIndex = 2,
|
||||
RowStartIndex = 0, // Including b/c it is required to be a "save selection"
|
||||
RowEndIndex = 10,
|
||||
IncludeHeaders = true // Including headers to test both column selection logic
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" },
|
||||
new DbCellValue { DisplayValue = "item3" },
|
||||
new DbCellValue { DisplayValue = "item4" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2")),
|
||||
new DbColumnWrapper(new TestDbColumn("column3")),
|
||||
new DbColumnWrapper(new TestDbColumn("column4"))
|
||||
RowEndIndex = 10
|
||||
};
|
||||
var (columns, data) = GetTestValues(4);
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have written two lines
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
string[] lines = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
Assert.AreEqual(2, lines.Length);
|
||||
// ... It should have written one line
|
||||
var lines = ParseWriterOutput(output, Environment.NewLine);
|
||||
Assert.AreEqual(1, lines.Length);
|
||||
|
||||
// ... It should have written a header line with two, comma separated names
|
||||
string[] headerValues = lines[0].Split(',');
|
||||
Assert.AreEqual(2, headerValues.Length);
|
||||
for (int i = 1; i <= 2; i++)
|
||||
{
|
||||
Assert.AreEqual(columns[i].ColumnName, headerValues[i - 1]);
|
||||
}
|
||||
|
||||
// ... The second line should have two, comma separated values
|
||||
string[] dataValues = lines[1].Split(',');
|
||||
// ... The line should have two, comma separated values
|
||||
string[] dataValues = lines[0].Split(',');
|
||||
Assert.AreEqual(2, dataValues.Length);
|
||||
for (int i = 1; i <= 2; i++)
|
||||
{
|
||||
@@ -216,7 +296,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRowWithCustomDelimiters()
|
||||
public void WriteRow_CustomDelimiter()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has custom delimiter say pipe("|") then this delimiter should be used
|
||||
@@ -227,35 +307,24 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
Delimiter = "|",
|
||||
IncludeHeaders = true
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
var (columns, data) = GetTestValues(2);
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have written two lines
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
string[] lines = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
||||
Assert.AreEqual(2, lines.Length);
|
||||
|
||||
// ... It should have written a header line with two, pipe("|") separated names
|
||||
string[] headerValues = lines[0].Split('|');
|
||||
Assert.AreEqual(2, headerValues.Length);
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
for (int i = 0; i < columns.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(columns[i].ColumnName, headerValues[i]);
|
||||
}
|
||||
@@ -264,144 +333,49 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRowsWithCustomLineSeperator()
|
||||
public void WriteRow_CustomLineSeparator()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has custom line seperator then this seperator should be used
|
||||
// ... Create a request params that has custom line separator
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
LineSeperator = "$$",
|
||||
IncludeHeaders = true
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
|
||||
byte[] output;
|
||||
string outputString;
|
||||
string[] lines;
|
||||
SaveAsCsvFileStreamWriter writer;
|
||||
|
||||
// If: I set default seperator and write a row
|
||||
requestParams.LineSeperator = null;
|
||||
output = new byte[8192];
|
||||
writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have splitten the lines by system's default line seperator
|
||||
outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
lines = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
Assert.AreEqual(2, lines.Length);
|
||||
|
||||
// If: I set \n (line feed) as seperator and write a row
|
||||
requestParams.LineSeperator = "\n";
|
||||
output = new byte[8192];
|
||||
writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have splitten the lines by \n
|
||||
outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
lines = outputString.Split(new[] { '\n' }, StringSplitOptions.None);
|
||||
Assert.AreEqual(2, lines.Length);
|
||||
|
||||
// If: I set \r\n (carriage return + line feed) as seperator and write a row
|
||||
requestParams.LineSeperator = "\r\n";
|
||||
output = new byte[8192];
|
||||
writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have splitten the lines by \r\n
|
||||
outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
lines = outputString.Split(new[] { "\r\n" }, StringSplitOptions.None);
|
||||
Assert.AreEqual(2, lines.Length);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRowWithCustomTextIdentifier()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has a text identifier set say single quotation marks("'") then this text identifier should be used
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams()
|
||||
{
|
||||
TextIdentifier = "\'",
|
||||
Delimiter = ";"
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item;1" },
|
||||
new DbCellValue { DisplayValue = "item,2" },
|
||||
new DbCellValue { DisplayValue = "item\"3" },
|
||||
new DbCellValue { DisplayValue = "item\'4" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2")),
|
||||
new DbColumnWrapper(new TestDbColumn("column3")),
|
||||
new DbColumnWrapper(new TestDbColumn("column4"))
|
||||
};
|
||||
var (columns, data) = GetTestValues(2);
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
// If: I set write a row
|
||||
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have splitten the columns by delimiter, embedded in text identifier when field contains delimiter or the text identifier
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
Assert.AreEqual("\'item;1\';item,2;item\"3;\'item\'\'4\'", outputString);
|
||||
// ... The lines should be split by the custom line separator
|
||||
var lines = ParseWriterOutput(output, "$$");
|
||||
Assert.AreEqual(2, lines.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRowWithCustomEncoding()
|
||||
public void WriteRow_CustomEncoding()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has custom delimiter say pipe("|") then this delimiter should be used
|
||||
// ... Create a request params that uses a custom encoding
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
Encoding = "Windows-1252"
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "ü" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1"))
|
||||
};
|
||||
var data = new[] { new DbCellValue { DisplayValue = "ü" } };
|
||||
var columns = new[] { new DbColumnWrapper(new TestDbColumn("column1")) };
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
@@ -414,5 +388,40 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
|
||||
}
|
||||
|
||||
private static (DbColumnWrapper[] columns, DbCellValue[] cells) GetTestValues(int columnCount)
|
||||
{
|
||||
var data = new DbCellValue[columnCount];
|
||||
var columns = new DbColumnWrapper[columnCount];
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
data[i] = new DbCellValue { DisplayValue = $"item{i}"};
|
||||
columns[i] = new DbColumnWrapper(new TestDbColumn($"column{i}"));
|
||||
}
|
||||
return (columns, data);
|
||||
}
|
||||
|
||||
private static SaveAsCsvFileStreamWriter GetWriterForEncodingTests(string delimiter, string identifier, string lineSeparator)
|
||||
{
|
||||
var settings = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
Delimiter = delimiter,
|
||||
IncludeHeaders = false,
|
||||
LineSeperator = lineSeparator,
|
||||
TextIdentifier = identifier,
|
||||
};
|
||||
var mockStream = Stream.Null;
|
||||
var mockColumns = Array.Empty<DbColumnWrapper>();
|
||||
return new SaveAsCsvFileStreamWriter(mockStream, settings, mockColumns);
|
||||
}
|
||||
|
||||
private static string[] ParseWriterOutput(byte[] output, string lineSeparator)
|
||||
{
|
||||
string outputString = Encoding.UTF8.GetString(output).Trim('\0');
|
||||
string[] lines = outputString.Split(new[] { lineSeparator }, StringSplitOptions.None);
|
||||
|
||||
// Make sure the file ends with a new line and return all but the meaningful lines
|
||||
Assert.IsEmpty(lines[lines.Length - 1]);
|
||||
return lines.Take(lines.Length - 1).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
@@ -27,7 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
|
||||
// If:
|
||||
// ... I create and then destruct a json writer
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams);
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams, Array.Empty<DbColumnWrapper>());
|
||||
jsonWriter.Dispose();
|
||||
|
||||
// Then:
|
||||
@@ -59,7 +59,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
|
||||
// If:
|
||||
// ... I write two rows
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams);
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams, columns);
|
||||
using (jsonWriter)
|
||||
{
|
||||
jsonWriter.WriteRow(data, columns);
|
||||
@@ -117,7 +117,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write two rows
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams);
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams, columns);
|
||||
using (jsonWriter)
|
||||
{
|
||||
jsonWriter.WriteRow(data, columns);
|
||||
@@ -158,7 +158,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
new DbCellValue {DisplayValue = "1", RawObject = 1},
|
||||
new DbCellValue {DisplayValue = "1.234", RawObject = 1.234},
|
||||
new DbCellValue {DisplayValue = "2017-07-08T00:00:00", RawObject = new DateTime(2017, 07, 08)},
|
||||
|
||||
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
@@ -170,7 +170,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
|
||||
// If:
|
||||
// ... I write two rows
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams);
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams, columns);
|
||||
using (jsonWriter)
|
||||
{
|
||||
jsonWriter.WriteRow(data, columns);
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
//
|
||||
// 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 System.Text;
|
||||
using System.Xml;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
{
|
||||
[TestFixture]
|
||||
public class SaveAsXmlFileStreamWriterTests
|
||||
{
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void ConstructAndDispose(bool formatted)
|
||||
{
|
||||
// Setup: Create test request and storage for the output
|
||||
var saveParams = new SaveResultsAsXmlRequestParams { Formatted = formatted };
|
||||
var columns = Array.Empty<DbColumnWrapper>();
|
||||
var output = new byte[8192];
|
||||
|
||||
// If: I create and dispose of an XML file writer
|
||||
var xmlWriter = new SaveAsXmlFileStreamWriter(new MemoryStream(output), saveParams, columns);
|
||||
xmlWriter.Dispose();
|
||||
|
||||
// Then:
|
||||
// ... The output should be just the XML node and the root node
|
||||
var rootNode = ParseOutput(output, Encoding.UTF8);
|
||||
Assert.IsEmpty(rootNode.ChildNodes);
|
||||
|
||||
// ... If the output is formatted, there should be multiple lines
|
||||
// otherwise, there should be only one line
|
||||
if (formatted)
|
||||
{
|
||||
CollectionAssert.Contains(output, (byte)'\n');
|
||||
}
|
||||
else
|
||||
{
|
||||
CollectionAssert.DoesNotContain(output, (byte)'\n');
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRow_WithoutColumnSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create request params that has no selection made
|
||||
// ... Create a set of data to write
|
||||
// ... Create storage for the output
|
||||
var saveParams = new SaveResultsAsXmlRequestParams();
|
||||
var data = new[]
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1", RawObject = "item1" },
|
||||
new DbCellValue { DisplayValue = "null", RawObject = null }
|
||||
};
|
||||
var columns = new[]
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
var output = new byte[8192];
|
||||
|
||||
// If: I write two rows
|
||||
using (var xmlWriter = new SaveAsXmlFileStreamWriter(new MemoryStream(output), saveParams, columns))
|
||||
{
|
||||
xmlWriter.WriteRow(data, columns);
|
||||
xmlWriter.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... XML should be well formed
|
||||
var rootNode = ParseOutput(output, Encoding.UTF8);
|
||||
|
||||
// ... Data node should have two nodes for the two rows
|
||||
Assert.AreEqual(2, rootNode.ChildNodes.Count);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
// ... Each row should have two nodes for the two cells
|
||||
var row = rootNode.ChildNodes[i];
|
||||
Assert.IsNotNull(row);
|
||||
Assert.AreEqual(2, row.ChildNodes.Count);
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
var cell = row.ChildNodes[j];
|
||||
Assert.IsNotNull(cell);
|
||||
|
||||
// ... Node name should be column name
|
||||
Assert.AreEqual(columns[j].ColumnName, cell.Name);
|
||||
|
||||
// ... Node value should be cell value
|
||||
if (data[j].RawObject == null)
|
||||
{
|
||||
Assert.IsEmpty(cell.InnerText);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(data[j].DisplayValue, cell.InnerText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRow_WithColumnSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create request params that has a selection made
|
||||
// ... Create a set of data to write
|
||||
// ... Create storage for the output
|
||||
var saveParams = new SaveResultsAsXmlRequestParams
|
||||
{
|
||||
ColumnEndIndex = 2,
|
||||
ColumnStartIndex = 1,
|
||||
RowEndIndex = 0, // Required for being considered a "selection"
|
||||
RowStartIndex = 0
|
||||
};
|
||||
var data = new[]
|
||||
{
|
||||
new DbCellValue { DisplayValue = "foo" },
|
||||
new DbCellValue { DisplayValue = "item1", RawObject = "item1" },
|
||||
new DbCellValue { DisplayValue = "null", RawObject = null },
|
||||
new DbCellValue { DisplayValue = "bar" }
|
||||
};
|
||||
var columns = new[]
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("ignoredCol")),
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2")),
|
||||
new DbColumnWrapper(new TestDbColumn("ignoredCol"))
|
||||
};
|
||||
var output = new byte[8192];
|
||||
|
||||
// If: I write two rows
|
||||
using (var xmlWriter = new SaveAsXmlFileStreamWriter(new MemoryStream(output), saveParams, columns))
|
||||
{
|
||||
xmlWriter.WriteRow(data, columns);
|
||||
xmlWriter.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... XML should be well formed
|
||||
var rootNode = ParseOutput(output, Encoding.UTF8);
|
||||
|
||||
// ... Data node should have two nodes for the two rows
|
||||
Assert.AreEqual(2, rootNode.ChildNodes.Count);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
// ... Each row should have two nodes for the two cells
|
||||
var row = rootNode.ChildNodes[i];
|
||||
Assert.IsNotNull(row);
|
||||
Assert.AreEqual(2, row.ChildNodes.Count);
|
||||
for (int j = 0; j < 1; j++)
|
||||
{
|
||||
var cell = row.ChildNodes[j];
|
||||
var columnIndex = j + 1;
|
||||
Assert.IsNotNull(cell);
|
||||
|
||||
// ... Node name should be column name
|
||||
Assert.AreEqual(columns[columnIndex].ColumnName, cell.Name);
|
||||
|
||||
// ... Node value should be cell value
|
||||
if (data[columnIndex].RawObject == null)
|
||||
{
|
||||
Assert.IsEmpty(cell.InnerText);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(data[columnIndex].DisplayValue, cell.InnerText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WriteRow_NonDefaultEncoding()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create request params that uses a special encoding
|
||||
// ... Create a set of data to write
|
||||
// ... Create storage for the output
|
||||
var saveParams = new SaveResultsAsXmlRequestParams { Encoding = "Windows-1252" };
|
||||
var data = new[] { new DbCellValue { DisplayValue = "ü", RawObject = "ü" } };
|
||||
var columns = new[] { new DbColumnWrapper(new TestDbColumn("column1")) };
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write the row
|
||||
using (var xmlWriter = new SaveAsXmlFileStreamWriter(new MemoryStream(output), saveParams, columns))
|
||||
{
|
||||
xmlWriter.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... The XML file should have been written properly in windows-1252 encoding
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
var encoding = Encoding.GetEncoding("Windows-1252");
|
||||
var rootNode = ParseOutput(output, encoding);
|
||||
|
||||
// ... The umlaut should be written using Windows-1252
|
||||
Assert.IsNotNull(rootNode.ChildNodes[0]); // <row>
|
||||
Assert.IsNotNull(rootNode.ChildNodes[0].ChildNodes[0]); // <column1>
|
||||
Assert.AreEqual(rootNode.ChildNodes[0].ChildNodes[0].InnerText, "ü");
|
||||
}
|
||||
|
||||
private XmlNode ParseOutput(byte[] bytes, Encoding encoding)
|
||||
{
|
||||
var outputString = encoding.GetString(bytes)
|
||||
.TrimStart(encoding.GetString(encoding.Preamble).ToCharArray()) // Trim any BOM
|
||||
.Trim('\0');
|
||||
var xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(outputString);
|
||||
|
||||
// Assert: Two elements at the root, XML and the root node
|
||||
Assert.AreEqual(2, xmlDoc.ChildNodes.Count);
|
||||
Assert.AreEqual("xml", xmlDoc.ChildNodes[0]?.Name);
|
||||
Assert.AreEqual("data", xmlDoc.ChildNodes[1]?.Name);
|
||||
|
||||
return xmlDoc.ChildNodes[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user