diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs index de407728..0b49fc36 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs @@ -77,6 +77,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts /// Include headers of columns in CSV /// public bool IncludeHeaders { get; set; } + + /// + /// Delimeter for separating data items in CSV + /// + public string Delimiter { get; set; } } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamWriter.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamWriter.cs index a23b2cbe..cbed6c53 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamWriter.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamWriter.cs @@ -48,13 +48,20 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// public override void WriteRow(IList row, IList columns) { + string delimiter = ","; + if(!string.IsNullOrEmpty(saveParams.Delimiter)) + { + delimiter = saveParams.Delimiter; + } + // Write out the header if we haven't already and the user chose to have it if (saveParams.IncludeHeaders && !headerWritten) { // Build the string var selectedColumns = columns.Skip(ColumnStartIndex ?? 0).Take(ColumnCount ?? columns.Count) .Select(c => EncodeCsvField(c.ColumnName) ?? string.Empty); - string headerLine = string.Join(",", selectedColumns); + + string headerLine = string.Join(delimiter, selectedColumns); // Encode it and write it out byte[] headerBytes = Encoding.UTF8.GetBytes(headerLine + Environment.NewLine); @@ -67,7 +74,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage var selectedCells = row.Skip(ColumnStartIndex ?? 0) .Take(ColumnCount ?? columns.Count) .Select(c => EncodeCsvField(c.DisplayValue)); - string rowLine = string.Join(",", selectedCells); + string rowLine = string.Join(delimiter, selectedCells); // Encode it and write it out byte[] rowBytes = Encoding.UTF8.GetBytes(rowLine + Environment.NewLine); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DataStorage/SaveAsCsvFileStreamWriterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DataStorage/SaveAsCsvFileStreamWriterTests.cs index 8d26e028..eb16afbc 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DataStorage/SaveAsCsvFileStreamWriterTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DataStorage/SaveAsCsvFileStreamWriterTests.cs @@ -99,7 +99,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage // 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 = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None); Assert.Equal(1, lines.Length); string[] values = lines[0].Split(','); Assert.Equal(2, values.Length); @@ -201,7 +201,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage Assert.Equal(2, headerValues.Length); for (int i = 1; i <= 2; i++) { - Assert.Equal(columns[i].ColumnName, headerValues[i-1]); + Assert.Equal(columns[i].ColumnName, headerValues[i - 1]); } // ... The second line should have two, comma separated values @@ -209,8 +209,57 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage Assert.Equal(2, dataValues.Length); for (int i = 1; i <= 2; i++) { - Assert.Equal(data[i].DisplayValue, dataValues[i-1]); + Assert.Equal(data[i].DisplayValue, dataValues[i - 1]); } } + + [Fact] + public void WriteRowWithCustomDelimeters() + { + // Setup: + // ... Create a request params that has custom delimeter say pipe("|") then this delimeter should be used + // ... Create a set of data to write + // ... Create a memory location to store the data + var requestParams = new SaveResultsAsCsvRequestParams + { + Delimiter = "|", + IncludeHeaders = true + }; + List data = new List + { + new DbCellValue { DisplayValue = "item1" }, + new DbCellValue { DisplayValue = "item2" } + }; + List columns = new List + { + 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.Equal(2, lines.Length); + + // ... It should have written a header line with two, pipe("|") separated names + string[] headerValues = lines[0].Split('|'); + Assert.Equal(2, headerValues.Length); + for (int i = 0; i < columns.Count; i++) + { + Assert.Equal(columns[i].ColumnName, headerValues[i]); + } + + // Note: No need to check values, it is done as part of the previous tests + } + } }