diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs index 295e6df9..d44b78f8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs @@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// Special action which this batch performed /// - private SpecialAction specialAction; + private readonly SpecialAction specialAction; #endregion @@ -128,7 +128,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// Localized timestamp for when the execution completed. /// Stored in UTC ISO 8601 format; should be localized before displaying to any user /// - public string ExecutionEndTimeStamp { get { return executionEndTime.ToString("o"); } } + public string ExecutionEndTimeStamp => executionEndTime.ToString("o"); /// /// Localized timestamp for how long it took for the execution to complete @@ -146,7 +146,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// Localized timestamp for when the execution began. /// Stored in UTC ISO 8601 format; should be localized before displaying to any user /// - public string ExecutionStartTimeStamp { get { return executionStartTime.ToString("o"); } } + public string ExecutionStartTimeStamp => executionStartTime.ToString("o"); /// /// Whether or not this batch encountered an error that halted execution @@ -161,15 +161,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// Ordinal of the batch in the query /// - public int Id { get; private set; } + public int Id { get; } /// /// The result sets of the batch execution /// - public IList ResultSets - { - get { return resultSets; } - } + public IList ResultSets => resultSets; /// /// Property for generating a set result set summaries from the result sets @@ -381,7 +378,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// Generates an execution plan /// /// The index for selecting the result set - /// An exeuction plan object + /// An execution plan object public Task GetExecutionPlan(int resultSetIndex) { ResultSet targetResultSet; diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamFactory.cs index ebfe173e..69d625e0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsCsvFileStreamFactory.cs @@ -6,6 +6,7 @@ using System; using System.IO; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Microsoft.SqlTools.ServiceLayer.SqlContext; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage { @@ -17,6 +18,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage { #region Properties + /// + /// Settings for query execution + /// + public QueryExecutionSettings QueryExecutionSettings { get; set; } + /// /// Parameters for the save as CSV request /// @@ -41,7 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// Stream reader public IFileStreamReader GetReader(string fileName) { - return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)); + return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read), QueryExecutionSettings); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsJsonFileStreamFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsJsonFileStreamFactory.cs index 02cde694..dc79686d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsJsonFileStreamFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/SaveAsJsonFileStreamFactory.cs @@ -6,6 +6,7 @@ using System; using System.IO; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Microsoft.SqlTools.ServiceLayer.SqlContext; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage { @@ -14,6 +15,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage #region Properties + /// + /// Settings for query execution + /// + public QueryExecutionSettings QueryExecutionSettings { get; set; } + /// /// Parameters for the save as JSON request /// @@ -28,7 +34,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage [Obsolete] public string CreateFile() { - throw new NotImplementedException(); + throw new InvalidOperationException(); } /// @@ -38,7 +44,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// Stream reader public IFileStreamReader GetReader(string fileName) { - return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)); + return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read), QueryExecutionSettings); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamFactory.cs index 60b819a6..9fd915c8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamFactory.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamFactory.cs @@ -4,6 +4,7 @@ // using System.IO; +using Microsoft.SqlTools.ServiceLayer.SqlContext; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage { @@ -15,14 +16,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage #region Properties /// - /// The maximum number of characters to store from long text fields + /// The settings for query execution /// - public int MaxCharsToStore { get; set; } - - /// - /// The maximum number of characters to store from xml fields - /// - public int MaxXmlCharsToStore { get; set; } + public QueryExecutionSettings ExecutionSettings { get; set; } #endregion @@ -43,7 +39,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// A public IFileStreamReader GetReader(string fileName) { - return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read)); + return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read), ExecutionSettings); } /// @@ -54,7 +50,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// A public IFileStreamWriter GetWriter(string fileName) { - return new ServiceBufferFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite), MaxCharsToStore, MaxXmlCharsToStore); + return new ServiceBufferFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite), ExecutionSettings); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs index e8bb72cf..f836e74d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs @@ -9,6 +9,8 @@ using System.Data.SqlTypes; using System.IO; using System.Text; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage { @@ -30,6 +32,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage private byte[] buffer; + private readonly QueryExecutionSettings executionSettings; + private readonly Stream fileStream; private readonly Dictionary> readMethods; @@ -40,8 +44,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// Constructs a new ServiceBufferFileStreamReader and initializes its state /// /// The filestream to read from - public ServiceBufferFileStreamReader(Stream stream) - { + /// The query execution settings + public ServiceBufferFileStreamReader(Stream stream, QueryExecutionSettings settings) + { + Validate.IsNotNull(nameof(stream), stream); + Validate.IsNotNull(nameof(settings), settings); + // Open file for reading/writing if (!stream.CanRead || !stream.CanSeek) { @@ -49,6 +57,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage } fileStream = stream; + executionSettings = settings; + // Create internal buffer buffer = new byte[DefaultBufferSize]; @@ -259,7 +269,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// A bool internal FileStreamReadResult ReadBoolean(long fileOffset) { - return ReadCellHelper(fileOffset, length => buffer[0] == 0x1); + // Override the stringifier with numeric values if the user prefers that + return ReadCellHelper(fileOffset, length => buffer[0] == 0x1, + toStringFunc: val => executionSettings.DisplayBitAsNumber + ? val ? "1" : "0" + : val.ToString()); } /// @@ -505,10 +519,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// /// + /// - public int TotalLength - { - get { return LengthLength + ValueLength; } - } + public int TotalLength => LengthLength + ValueLength; } #region IDisposable Implementation diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs index e46d1227..c56d43e1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.IO; using System.Text; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage @@ -24,8 +25,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage #region Member Variables private readonly Stream fileStream; - private readonly int maxCharsToStore; - private readonly int maxXmlCharsToStore; + private readonly QueryExecutionSettings executionSettings; private byte[] byteBuffer; private readonly short[] shortBuffer; @@ -46,16 +46,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// Constructs a new writer /// /// The file wrapper to use as the underlying file stream - /// Maximum number of characters to store for long text fields - /// Maximum number of characters to store for XML fields - public ServiceBufferFileStreamWriter(Stream stream, int maxCharsToStore, int maxXmlCharsToStore) + /// The query execution settings + public ServiceBufferFileStreamWriter(Stream stream, QueryExecutionSettings settings) { + Validate.IsNotNull(nameof(stream), stream); + Validate.IsNotNull(nameof(settings), settings); + // open file for reading/writing if (!stream.CanWrite || !stream.CanSeek) { throw new InvalidOperationException("Stream must be writable and seekable."); } fileStream = stream; + executionSettings = settings; // create internal buffer byteBuffer = new byte[DefaultBufferLength]; @@ -69,10 +72,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage doubleBuffer = new double[1]; floatBuffer = new float[1]; - // Store max chars to store - this.maxCharsToStore = maxCharsToStore; - this.maxXmlCharsToStore = maxXmlCharsToStore; - // Define what methods to use to write a type to the file writeMethods = new Dictionary> { @@ -145,18 +144,18 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage // this is a long field if (ci.IsBytes) { - values[i] = reader.GetBytesWithMaxCapacity(i, maxCharsToStore); + values[i] = reader.GetBytesWithMaxCapacity(i, executionSettings.MaxCharsToStore); } else if (ci.IsChars) { - Debug.Assert(maxCharsToStore > 0); - values[i] = reader.GetCharsWithMaxCapacity(i, - ci.IsXml ? maxXmlCharsToStore : maxCharsToStore); + int maxChars = ci.IsXml + ? executionSettings.MaxXmlCharsToStore + : executionSettings.MaxCharsToStore; + values[i] = reader.GetCharsWithMaxCapacity(i, maxChars); } else if (ci.IsXml) { - Debug.Assert(maxXmlCharsToStore > 0); - values[i] = reader.GetXmlWithMaxCapacity(i, maxXmlCharsToStore); + values[i] = reader.GetXmlWithMaxCapacity(i, executionSettings.MaxXmlCharsToStore); } else { diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs index 702cabb0..e6db27ec 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs @@ -53,16 +53,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// private bool hasExecuteBeenCalled; - /// - /// Settings for query runtime - /// - private QueryExecutionSettings querySettings; - - /// - /// Streaming output factory for the query - /// - private IFileStreamFactory streamOutputFactory; - /// /// Name of the new database if the database name was changed in the query /// @@ -109,8 +99,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution QueryText = queryText; editorConnection = connection; cancellationSource = new CancellationTokenSource(); - querySettings = settings; - streamOutputFactory = outputFactory; // Process the query into batches BatchParserWrapper parser = new BatchParserWrapper(); @@ -135,16 +123,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution if (DoesSupportExecutionPlan(connection)) { // Checking settings for execution plan options - if (querySettings.ExecutionPlanOptions.IncludeEstimatedExecutionPlanXml) + if (settings.ExecutionPlanOptions.IncludeEstimatedExecutionPlanXml) { // Enable set showplan xml - addBatch(string.Format(SetShowPlanXml, On), BeforeBatches, streamOutputFactory); - addBatch(string.Format(SetShowPlanXml, Off), AfterBatches, streamOutputFactory); + AddBatch(string.Format(SetShowPlanXml, On), BeforeBatches, outputFactory); + AddBatch(string.Format(SetShowPlanXml, Off), AfterBatches, outputFactory); } - else if (querySettings.ExecutionPlanOptions.IncludeActualExecutionPlanXml) + else if (settings.ExecutionPlanOptions.IncludeActualExecutionPlanXml) { - addBatch(string.Format(SetStatisticsXml, On), BeforeBatches, streamOutputFactory); - addBatch(string.Format(SetStatisticsXml, Off), AfterBatches, streamOutputFactory); + AddBatch(string.Format(SetStatisticsXml, On), BeforeBatches, outputFactory); + AddBatch(string.Format(SetStatisticsXml, Off), AfterBatches, outputFactory); } } } @@ -182,11 +170,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public event QueryAsyncEventHandler QueryFailed; - /// - /// Callback for when the query connection has failed - /// - public event QueryAsyncErrorEventHandler QueryConnectionException; - /// /// Event to be called when a resultset has completed. /// @@ -459,7 +442,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// Function to add a new batch to a Batch set /// - private void addBatch(string query, List batchSet, IFileStreamFactory outputFactory) + private static void AddBatch(string query, ICollection batchSet, IFileStreamFactory outputFactory) { batchSet.Add(new Batch(query, null, batchSet.Count, outputFactory)); } diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs index 999c126d..da9816d8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -64,11 +64,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { get { - return BufferFileStreamFactory ?? (BufferFileStreamFactory = new ServiceBufferFileStreamFactory + if (BufferFileStreamFactory == null) { - MaxCharsToStore = Settings.SqlTools.QueryExecutionSettings.MaxCharsToStore, - MaxXmlCharsToStore = Settings.SqlTools.QueryExecutionSettings.MaxXmlCharsToStore - }); + BufferFileStreamFactory = new ServiceBufferFileStreamFactory + { + ExecutionSettings = Settings.QueryExecutionSettings + }; + } + return BufferFileStreamFactory; } } @@ -295,7 +298,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution // Use the default CSV file factory if we haven't overridden it IFileStreamFactory csvFactory = CsvFileFactory ?? new SaveAsCsvFileStreamFactory { - SaveRequestParams = saveParams + SaveRequestParams = saveParams, + QueryExecutionSettings = Settings.QueryExecutionSettings }; await SaveResultsHelper(saveParams, requestContext, csvFactory); } @@ -309,7 +313,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution // Use the default JSON file factory if we haven't overridden it IFileStreamFactory jsonFactory = JsonFileFactory ?? new SaveAsJsonFileStreamFactory { - SaveRequestParams = saveParams + SaveRequestParams = saveParams, + QueryExecutionSettings = Settings.QueryExecutionSettings }; await SaveResultsHelper(saveParams, requestContext, jsonFactory); } @@ -452,20 +457,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution await eventSender.SendEvent(QueryCompleteEvent.Type, eventParams); }; - Query.QueryAsyncErrorEventHandler errorCallback = async errorMessage => - { - // Send back the error message - QueryCompleteParams eventParams = new QueryCompleteParams - { - OwnerUri = ownerUri, - //Message = errorMessage - }; - await eventSender.SendEvent(QueryCompleteEvent.Type, eventParams); - }; - query.QueryCompleted += callback; query.QueryFailed += callback; - query.QueryConnectionException += errorCallback; // Setup the batch callbacks Batch.BatchAsyncEventHandler batchStartCallback = async b => diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs index cf0e8b67..b6adeb33 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs @@ -25,8 +25,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution #region Constants // Column names of 'for xml' and 'for json' queries - private const string NameOfForXMLColumn = "XML_F52E2B61-18A1-11d1-B105-00805F49916B"; - private const string NameOfForJSONColumn = "JSON_F52E2B61-18A1-11d1-B105-00805F49916B"; + private const string NameOfForXmlColumn = "XML_F52E2B61-18A1-11d1-B105-00805F49916B"; + private const string NameOfForJsonColumn = "JSON_F52E2B61-18A1-11d1-B105-00805F49916B"; private const string YukonXmlShowPlanColumn = "Microsoft SQL Server 2005 XML Showplan"; #endregion @@ -72,7 +72,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// The special action which applied to this result set /// - private SpecialAction specialAction; + private readonly SpecialAction specialAction; #endregion @@ -251,7 +251,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// An execution plan object public Task GetExecutionPlan() { - // Proccess the action just incase is hasn't been yet + // Process the action just incase is hasn't been yet ProcessSpecialAction(); // Sanity check to make sure that the results have been read beforehand @@ -260,7 +260,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution throw new InvalidOperationException(SR.QueryServiceResultSetNotRead); } // Check that we this result set contains a showplan - else if (!specialAction.ExpectYukonXMLShowPlan) + if (!specialAction.ExpectYukonXMLShowPlan) { throw new Exception(SR.QueryServiceExecutionPlanNotFound); } @@ -268,7 +268,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution return Task.Factory.StartNew(() => { - string content = null; + string content; string format = null; using (IFileStreamReader fileStreamReader = fileStreamFactory.GetReader(outputFileName)) @@ -333,6 +333,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution } } + /// + /// Saves the contents of this result set to a file using the IFileStreamFactory provided + /// + /// Parameters for saving the results to a file + /// + /// Factory for creating a stream reader/writer combo for writing results to disk + /// + /// Handler for a successful write of all rows + /// Handler for unsuccessful write of all rows public void SaveAs(SaveResultsRequestParams saveParams, IFileStreamFactory fileFactory, SaveAsAsyncEventHandler successHandler, SaveAsFailureAsyncEventHandler failureHandler) { @@ -475,13 +484,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution if (Columns?.Length == 1 && RowCount != 0) { - if (Columns[0].ColumnName.Equals(NameOfForXMLColumn, StringComparison.Ordinal)) + if (Columns[0].ColumnName.Equals(NameOfForXmlColumn, StringComparison.Ordinal)) { Columns[0].IsXml = true; isSingleColumnXmlJsonResultSet = true; RowCount = 1; } - else if (Columns[0].ColumnName.Equals(NameOfForJSONColumn, StringComparison.Ordinal)) + else if (Columns[0].ColumnName.Equals(NameOfForJsonColumn, StringComparison.Ordinal)) { Columns[0].IsJson = true; isSingleColumnXmlJsonResultSet = true; diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/QueryExecutionSettings.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/QueryExecutionSettings.cs index cd269758..5dccf800 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/QueryExecutionSettings.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/QueryExecutionSettings.cs @@ -2,6 +2,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // + using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; namespace Microsoft.SqlTools.ServiceLayer.SqlContext @@ -33,12 +34,17 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext /// Default selection of returning an actual XML showplan with all batches /// Do not return any execution plan by default /// - private ExecutionPlanOptions DefaultExecutionPlanOptions = new ExecutionPlanOptions() + private static readonly ExecutionPlanOptions DefaultExecutionPlanOptions = new ExecutionPlanOptions { IncludeActualExecutionPlanXml = false, IncludeEstimatedExecutionPlanXml = false }; + /// + /// Default option for displaying a bit column as a number. (defacto standard as per SSMS) + /// + private const bool DefaultDisplayBitAsNumber = true; + #endregion #region Member Variables @@ -51,6 +57,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext private ExecutionPlanOptions? executionPlanOptions; + private bool? displayBitAsNumber; + #endregion #region Properties @@ -64,24 +72,46 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext set { batchSeparator = value; } } + /// + /// Maximum number of characters to store in temp file for long character fields and binary + /// fields. Will use a default if a value was not configured. + /// public int MaxCharsToStore { get { return maxCharsToStore ?? DefaultMaxCharsToStore; } set { maxCharsToStore = value; } } + /// + /// Maximum number of characters to store in temp file for XML columns. Will use a default + /// value if one was not configured. + /// public int MaxXmlCharsToStore { get { return maxXmlCharsToStore ?? DefaultMaxXmlCharsToStore; } set { maxXmlCharsToStore = value; } } + /// + /// Options for returning execution plans when executing queries + /// public ExecutionPlanOptions ExecutionPlanOptions { get { return executionPlanOptions ?? DefaultExecutionPlanOptions; } set { executionPlanOptions = value; } } + /// + /// Determines how to generate display value for bit columns. If true, bit columns + /// will be rendered as "1" or "0". If false, bit columns will be rendered as + /// "true" or "false" + /// + public bool DisplayBitAsNumber + { + get { return displayBitAsNumber ?? DefaultDisplayBitAsNumber; } + set { displayBitAsNumber = value; } + } + #endregion #region Public Methods @@ -96,6 +126,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext MaxCharsToStore = newSettings.MaxCharsToStore; MaxXmlCharsToStore = newSettings.MaxXmlCharsToStore; ExecutionPlanOptions = newSettings.ExecutionPlanOptions; + DisplayBitAsNumber = newSettings.DisplayBitAsNumber; } #endregion diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs index 1ed51b65..a9f1e4f7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs @@ -111,9 +111,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common return fileName; }); mock.Setup(fsf => fsf.GetReader(It.IsAny())) - .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]))); + .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]), new QueryExecutionSettings())); mock.Setup(fsf => fsf.GetWriter(It.IsAny())) - .Returns(output => new ServiceBufferFileStreamWriter(new MemoryStream(storage[output]), 1024, 1024)); + .Returns(output => new ServiceBufferFileStreamWriter(new MemoryStream(storage[output]), new QueryExecutionSettings())); return mock.Object; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs index 53fe76ec..0cd1b691 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs @@ -148,9 +148,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution return fileName; }); mock.Setup(fsf => fsf.GetReader(It.IsAny())) - .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]))); + .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]), new QueryExecutionSettings())); mock.Setup(fsf => fsf.GetWriter(It.IsAny())) - .Returns(output => new ServiceBufferFileStreamWriter(new MemoryStream(storage[output]), 1024, 1024)); + .Returns(output => new ServiceBufferFileStreamWriter(new MemoryStream(storage[output]), new QueryExecutionSettings())); return mock.Object; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs index d7c2e9b3..43f776b7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs @@ -12,6 +12,7 @@ using System.Text; using System.Text.RegularExpressions; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; +using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Moq; using Xunit; @@ -20,6 +21,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage { public class ReaderWriterPairTest { + [Fact] + public void ReaderStreamNull() + { + // If: I create a service buffer file stream reader with a null stream + // Then: It should throw an exception + Assert.Throws(() => new ServiceBufferFileStreamReader(null, new QueryExecutionSettings())); + } + + [Fact] + public void ReaderSettingsNull() + { + // If: I create a service buffer file stream reader with null settings + // Then: It should throw an exception + Assert.Throws(() => new ServiceBufferFileStreamReader(Stream.Null, null)); + } + [Fact] public void ReaderInvalidStreamCannotRead() { @@ -30,7 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage invalidStream.SetupGet(s => s.CanSeek).Returns(true); Assert.Throws(() => { - ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object); + ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object, new QueryExecutionSettings()); obj.Dispose(); }); } @@ -45,11 +62,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage invalidStream.SetupGet(s => s.CanSeek).Returns(false); Assert.Throws(() => { - ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object); + ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object, new QueryExecutionSettings()); obj.Dispose(); }); } + [Fact] + public void WriterStreamNull() + { + // If: I create a service buffer file stream writer with a null stream + // Then: It should throw an exception + Assert.Throws(() => new ServiceBufferFileStreamWriter(null, new QueryExecutionSettings())); + } + + [Fact] + public void WriterSettingsNull() + { + // If: I create a service buffer file stream writer with null settings + // Then: It should throw an exception + Assert.Throws(() => new ServiceBufferFileStreamWriter(Stream.Null, null)); + } + [Fact] public void WriterInvalidStreamCannotWrite() { @@ -60,7 +93,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage invalidStream.SetupGet(s => s.CanSeek).Returns(true); Assert.Throws(() => { - ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, 1024, 1024); + ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, new QueryExecutionSettings()); obj.Dispose(); }); } @@ -75,20 +108,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage invalidStream.SetupGet(s => s.CanSeek).Returns(false); Assert.Throws(() => { - ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, 1024, 1024); + ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, new QueryExecutionSettings()); obj.Dispose(); }); } [SuppressMessage("ReSharper", "UnusedParameter.Local")] - private static string VerifyReadWrite(int valueLength, T value, Func writeFunc, Func readFunc) + private static string VerifyReadWrite(int valueLength, T value, + Func writeFunc, + Func readFunc, + QueryExecutionSettings overrideSettings = null) { // Setup: Create a mock file stream byte[] storage = new byte[8192]; + overrideSettings = overrideSettings ?? new QueryExecutionSettings(); // If: // ... I write a type T to the writer - using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(new MemoryStream(storage), 10, 10)) + using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(new MemoryStream(storage), overrideSettings)) { int writtenBytes = writeFunc(writer, value); Assert.Equal(valueLength, writtenBytes); @@ -96,7 +133,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage // ... And read the type T back FileStreamReadResult outValue; - using (ServiceBufferFileStreamReader reader = new ServiceBufferFileStreamReader(new MemoryStream(storage))) + using (ServiceBufferFileStreamReader reader = new ServiceBufferFileStreamReader(new MemoryStream(storage), overrideSettings)) { outValue = readFunc(reader); } @@ -166,11 +203,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage } [Theory] - [InlineData(true)] - [InlineData(false)] - public void Boolean(bool value) + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void Boolean(bool value, bool preferNumeric) { - VerifyReadWrite(sizeof(bool) + 1, value, (writer, val) => writer.WriteBoolean(val), reader => reader.ReadBoolean(0)); + string displayValue = VerifyReadWrite(sizeof(bool) + 1, value, + (writer, val) => writer.WriteBoolean(val), + reader => reader.ReadBoolean(0), + new QueryExecutionSettings {DisplayBitAsNumber = preferNumeric} + ); + + // Validate the display value + if (preferNumeric) + { + int output; + Assert.True(int.TryParse(displayValue, out output)); + } + else + { + bool output; + Assert.True(bool.TryParse(displayValue, out output)); + } } [Theory] @@ -396,7 +451,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage { // If: // ... I write null as a string to the writer - using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, 10, 10)) + using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, new QueryExecutionSettings())) { // Then: // ... I should get an argument null exception @@ -433,7 +488,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage { // If: // ... I write null as a string to the writer - using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, 10, 10)) + using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, new QueryExecutionSettings())) { // Then: // ... I should get an argument null exception diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResults/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResults/ServiceIntegrationTests.cs index 05b0393e..80cb655a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResults/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResults/ServiceIntegrationTests.cs @@ -269,7 +269,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.SaveResults { Mock mock = new Mock(); mock.Setup(fsf => fsf.GetReader(It.IsAny())) - .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]))); + .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]), new QueryExecutionSettings())); mock.Setup(fsf => fsf.GetWriter(It.IsAny())) .Returns(output => { @@ -284,7 +284,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.SaveResults { Mock mock = new Mock(); mock.Setup(fsf => fsf.GetReader(It.IsAny())) - .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]))); + .Returns(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]), new QueryExecutionSettings())); mock.Setup(fsf => fsf.GetWriter(It.IsAny())) .Returns(output => {