From b8e46ce65ffed87978542db3d53d03d37d6fc7db Mon Sep 17 00:00:00 2001 From: Kevin Cunnane Date: Wed, 22 Nov 2017 11:33:19 -0800 Subject: [PATCH] Support `GO N` syntax to execute multiple times (#551) * Support `GO N` syntax to execute multiple times - Plumbed through the batch execution count from the parser and used in the batch execution code path - Functionality matches SSMS: - Outputs loop start/end messages that match SSMS if you're doing multi-batch execution - Outputs an "ignoring failure" error if an error happens during a batch - Added tests for this - Manually verified end to end also * Fixing test error --- .../Localization/sr.cs | 112 ++++----- .../Localization/sr.resx | 238 +++++++++--------- .../BatchParser/BatchParserWrapper.cs | 120 ++++----- .../BatchParser/ExecutionEngineCode/Batch.cs | 18 ++ .../ExecutionEngineCode/BatchDefinition.cs | 16 +- .../ExecutionEngineCode/ExecutionEngine.cs | 16 +- .../Localization/sr.cs | 6 +- .../Localization/sr.de.resx | 10 +- .../Localization/sr.es.resx | 10 +- .../Localization/sr.fr.resx | 10 +- .../Localization/sr.it.resx | 10 +- .../Localization/sr.ja.resx | 17 +- .../Localization/sr.ko.resx | 10 +- .../Localization/sr.pt-BR.resx | 10 +- .../Localization/sr.resx | 6 +- .../Localization/sr.ru.resx | 4 +- .../Localization/sr.strings | 4 +- .../Localization/sr.xlf | 6 +- .../Localization/sr.zh-hans.resx | 14 +- .../Localization/sr.zh-hant.resx | 10 +- .../Localization/transXliff/sr.de.xlf | 2 +- .../Localization/transXliff/sr.es.xlf | 2 +- .../Localization/transXliff/sr.fr.xlf | 2 +- .../Localization/transXliff/sr.it.xlf | 2 +- .../Localization/transXliff/sr.ja.xlf | 2 +- .../Localization/transXliff/sr.ko.xlf | 2 +- .../Localization/transXliff/sr.pt-BR.xlf | 2 +- .../Localization/transXliff/sr.ru.xlf | 2 +- .../Localization/transXliff/sr.zh-hans.xlf | 2 +- .../Localization/transXliff/sr.zh-hant.xlf | 2 +- .../QueryExecution/Batch.cs | 223 +++++++++------- .../QueryExecution/Query.cs | 5 +- .../BatchParser/BatchParserWrapperTests.cs | 17 +- .../QueryExecution/Execution/BatchTests.cs | 76 ++++++ .../Utility/SrTests.cs | 2 +- 35 files changed, 623 insertions(+), 367 deletions(-) diff --git a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.cs b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.cs index 8db98dd4..e2822c02 100755 --- a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.cs @@ -1,61 +1,61 @@ -// WARNING: -// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0 -// from information in sr.strings -// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN -// -namespace Microsoft.SqlTools.ResourceProvider -{ - using System; - using System.Reflection; - using System.Resources; - using System.Globalization; +// WARNING: +// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0 +// from information in sr.strings +// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN +// +namespace Microsoft.SqlTools.ResourceProvider +{ + using System; + using System.Reflection; + using System.Resources; + using System.Globalization; + + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SR + { + protected SR() + { } + + public static CultureInfo Culture + { + get + { + return Keys.Culture; + } + set + { + Keys.Culture = value; + } + } - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class SR - { - protected SR() - { } + + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Keys + { + static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.Localization.SR", typeof(SR).GetTypeInfo().Assembly); + + static CultureInfo _culture = null; - public static CultureInfo Culture - { - get - { - return Keys.Culture; - } - set - { - Keys.Culture = value; - } - } - - - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Keys - { - static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.Localization.SR", typeof(SR).GetTypeInfo().Assembly); - - static CultureInfo _culture = null; - - - private Keys() - { } - - public static CultureInfo Culture - { - get - { - return _culture; - } - set - { - _culture = value; - } - } - - public static string GetString(string key) - { - return resourceManager.GetString(key, _culture); - } + + private Keys() + { } + + public static CultureInfo Culture + { + get + { + return _culture; + } + set + { + _culture = value; + } + } + + public static string GetString(string key) + { + return resourceManager.GetString(key, _culture); + } } } diff --git a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.resx b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.resx index 09ac8ab9..845db20a 100755 --- a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/BatchParserWrapper.cs b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/BatchParserWrapper.cs index b12f40ad..dd9966b0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/BatchParserWrapper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/BatchParserWrapper.cs @@ -17,52 +17,49 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser /// public sealed class BatchParserWrapper : IDisposable { - - private List> startLineColumns; - private List lengths; + private List batchInfos; private ExecutionEngine executionEngine; private BatchEventNotificationHandler notificationHandler; /// /// Helper method used to Convert line/column information in a file to offset /// - private static List ConvertToBatchDefinitionList(string content, - IList> positions, List lengths) + private static List ConvertToBatchDefinitionList(string content, List batchInfos) { List batchDefinitionList = new List(); - if (positions.Count == 0 && lengths.Count == 0) + if (batchInfos.Count == 0) { return batchDefinitionList; } - List offsets = GetOffsets(content, positions); + List offsets = GetOffsets(content, batchInfos); - if (!string.IsNullOrEmpty(content) && (positions.Count > 0)) + if (!string.IsNullOrEmpty(content) && (batchInfos.Count > 0)) { // Instantiate a string reader for the whole sql content using (StringReader reader = new StringReader(content)) { // Generate the first batch definition list - int startLine = positions[0].Item1 + 1; //positions is 0 index based + int startLine = batchInfos[0].startLine + 1; //positions is 0 index based int endLine = startLine; int lineDifference = startLine - 1; int endColumn; int offset = offsets[0]; - int startColumn = positions[0].Item2; - int count = positions.Count; - string batchText = content.Substring(offset, lengths[0]); + int startColumn = batchInfos[0].startColumn; + int count = batchInfos.Count; + string batchText = content.Substring(offset, batchInfos[0].length); // if there's only one batch then the line difference is just startLine if (count > 1) { - lineDifference = positions[1].Item1 - positions[0].Item1; + lineDifference = batchInfos[1].startLine - batchInfos[0].startLine; } // get endLine, endColumn for the current batch and the lineStartOffset for the next batch - var batchInfo = ReadLines(reader, lineDifference, endLine); - endLine = batchInfo.Item1; - endColumn = batchInfo.Item2; + var position = ReadLines(reader, lineDifference, endLine); + endLine = position.Item1; + endColumn = position.Item2; // create a new BatchDefinition and add it to the list BatchDefinition batchDef = new BatchDefinition( @@ -70,27 +67,28 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser startLine, endLine, startColumn + 1, - endColumn + 1 + endColumn + 1, + batchInfos[0].executionCount ); batchDefinitionList.Add(batchDef); if (count > 1) { - offset = offsets[1] + positions[0].Item2; + offset = offsets[1] + batchInfos[0].startColumn; } // Generate the rest batch definitions for (int index = 1; index < count - 1; index++) { - lineDifference = positions[index + 1].Item1 - positions[index].Item1; - batchInfo = ReadLines(reader, lineDifference, endLine); - endLine = batchInfo.Item1; - endColumn = batchInfo.Item2; + lineDifference = batchInfos[index + 1].startLine - batchInfos[index].startLine; + position = ReadLines(reader, lineDifference, endLine); + endLine = position.Item1; + endColumn = position.Item2; offset = offsets[index]; - batchText = content.Substring(offset, lengths[index]); - startLine = positions[index].Item1; - startColumn = positions[index].Item2; + batchText = content.Substring(offset, batchInfos[index].length); + startLine = batchInfos[index].startLine; + startColumn = batchInfos[index].startColumn; // make a new batch definition for each batch BatchDefinition batch = new BatchDefinition( @@ -98,7 +96,8 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser startLine, endLine, startColumn + 1, - endColumn + 1 + endColumn + 1, + batchInfos[index].executionCount ); batchDefinitionList.Add(batch); } @@ -107,8 +106,8 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser if (count > 1) { - batchText = content.Substring(offsets[count-1], lengths[count - 1]); - BatchDefinition lastBatchDef = GetLastBatchDefinition(reader, positions[count - 1], batchText); + batchText = content.Substring(offsets[count-1], batchInfos[count - 1].length); + BatchDefinition lastBatchDef = GetLastBatchDefinition(reader, batchInfos[count - 1], batchText); batchDefinitionList.Add(lastBatchDef); } @@ -117,14 +116,14 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser return batchDefinitionList; } - private static int GetMaxStartLine(IList> positions) + private static int GetMaxStartLine(IList batchInfos) { int highest = 0; - foreach (var position in positions) + foreach (var batchInfo in batchInfos) { - if (position.Item1 > highest) + if (batchInfo.startLine > highest) { - highest = position.Item1; + highest = batchInfo.startLine; } } return highest; @@ -133,14 +132,14 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser /// /// Gets offsets for all batches /// - private static List GetOffsets(string content, IList> positions) + private static List GetOffsets(string content, IList batchInfos) { List offsets = new List(); int count = 0; int offset = 0; bool foundAllOffsets = false; - int maxStartLine = GetMaxStartLine(positions); + int maxStartLine = GetMaxStartLine(batchInfos); using (StringReader reader = new StringReader(content)) { // go until we have found offsets for all batches @@ -150,7 +149,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser for (int i = 0; i <= maxStartLine ; i++) { // get offset for the current batch - ReadLines(reader, ref count, ref offset, ref foundAllOffsets, positions, offsets, i); + ReadLines(reader, ref count, ref offset, ref foundAllOffsets, batchInfos, offsets, i); // if we found all the offsets, then we're done if (foundAllOffsets) @@ -168,16 +167,16 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser /// Helper function to read lines of batches to get offsets /// private static void ReadLines(StringReader reader, ref int count, ref int offset, ref bool foundAllOffsets, - IList> positions, List offsets, int iteration) + IList batchInfos, List offsets, int iteration) { int ch; while (true) { - if (positions[count].Item1 == iteration) + if (batchInfos[count].startLine == iteration) { count++; offsets.Add(offset); - if (count == positions.Count) + if (count == batchInfos.Count) { foundAllOffsets = true; break; @@ -205,10 +204,10 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser /// Helper method to get the last batch /// private static BatchDefinition GetLastBatchDefinition(StringReader reader, - Tuple position, string batchText) + BatchInfo batchInfo, string batchText) { - int startLine = position.Item1; - int startColumn = position.Item2; + int startLine = batchInfo.startLine; + int startColumn = batchInfo.startColumn; string prevLine = null; string line = reader.ReadLine(); int endLine = startLine; @@ -232,7 +231,8 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser startLine, endLine, startColumn + 1, - endColumn + 1 + endColumn + 1, + batchInfo.executionCount ); } @@ -240,7 +240,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser /// Helper function to get correct lines and columns /// in a single batch with multiple statements /// - private static Tuple GetBatchDetails(StringReader reader, int endLine) + private static Tuple GetBatchPositionDetails(StringReader reader, int endLine) { string prevLine = null; string line = reader.ReadLine(); @@ -274,7 +274,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser // if only one batch with multiple lines if (n == 0) { - return GetBatchDetails(reader, endLine); + return GetBatchPositionDetails(reader, endLine); } // if there are more than one batch @@ -327,15 +327,13 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser /// public List GetBatches(string sqlScript) { - startLineColumns = new List>(); - lengths = new List(); + batchInfos = new List(); // execute the script - all communication / integration after here happen via event handlers executionEngine.ParseScript(sqlScript, notificationHandler); // retrieve a list of BatchDefinitions - List batchDefinitionList = ConvertToBatchDefinitionList(sqlScript, startLineColumns, - lengths); + List batchDefinitionList = ConvertToBatchDefinitionList(sqlScript, batchInfos); return batchDefinitionList; } @@ -360,10 +358,6 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser { if (args != null && args.Batch != null) { - - Tuple position = new Tuple(args.Batch.TextSpan.iStartLine, args.Batch.TextSpan.iStartIndex); - - // PS168371 // // There is a bug in the batch parser where it appends a '\n' to the end of the last @@ -383,8 +377,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser } // Add the script info - startLineColumns.Add(position); - lengths.Add(batchTextLength); + batchInfos.Add(new BatchInfo(args.Batch.TextSpan.iStartLine, args.Batch.TextSpan.iStartIndex, batchTextLength, args.Batch.ExpectedExecutionCount)); } } catch (NotImplementedException) @@ -469,11 +462,26 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser { executionEngine.Dispose(); executionEngine = null; - startLineColumns = null; - lengths = null; + batchInfos = null; } } } #endregion + + private class BatchInfo + { + public BatchInfo(int startLine, int startColumn, int length, int repeatCount = 1) + { + this.startLine = startLine; + this.startColumn = startColumn; + this.length = length; + this.executionCount = repeatCount; + } + public int startLine; + public int startColumn; + public int length; + public int executionCount; + } + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/Batch.cs index 32f98b9e..87eda717 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/Batch.cs @@ -214,6 +214,8 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode //index of the batch in collection of batches private int index = 0; + private int expectedExecutionCount = 1; + private long totalAffectedRows = 0; private bool hasErrors; @@ -343,6 +345,22 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode } } + /// + /// The number of times this batch is expected to be executed. Will be 1 by default, but for statements + /// with "GO 2" or other numerical values, will have a number > 1 + /// + public int ExpectedExecutionCount + { + get + { + return expectedExecutionCount; + } + + set + { + expectedExecutionCount = value; + } + } /// /// Returns how many rows were affected. It should be the value that can be shown diff --git a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/BatchDefinition.cs b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/BatchDefinition.cs index 954a8bf9..178bbb59 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/BatchDefinition.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/BatchDefinition.cs @@ -13,13 +13,15 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode /// /// Constructor method for a BatchDefinition /// - public BatchDefinition(string batchText, int startLine, int endLine, int startColumn, int endColumn) + public BatchDefinition(string batchText, int startLine, int endLine, int startColumn, int endColumn, int executionCount) { BatchText = batchText; StartLine = startLine; EndLine = endLine; StartColumn = startColumn; EndColumn = endColumn; + // set the batch execution count, with min value of 1 + BatchExecutionCount = executionCount > 0 ? executionCount : 1; } /// @@ -55,11 +57,19 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode } /// - /// Get batch text assocaited with the BatchDefinition + /// Get batch text associated with the BatchDefinition /// public string BatchText { get; private set; - } + } + + /// + /// Get number of times to execute this batch + /// + public int BatchExecutionCount + { + get; private set; + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/ExecutionEngine.cs b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/ExecutionEngine.cs index 6d524003..933a23a2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/ExecutionEngine.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/BatchParser/ExecutionEngineCode/ExecutionEngine.cs @@ -72,7 +72,6 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode { try { - // Parsing mode we execute only once if (conditions.IsParseOnly) { numBatchExecutionTimes = 1; @@ -81,7 +80,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode int timesLoop = numBatchExecutionTimes; if (numBatchExecutionTimes > 1) { - RaiseBatchMessage(String.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_InitilizingLoop, numBatchExecutionTimes)); + RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_InitializingLoop)); } while (timesLoop > 0 && result != ScriptExecutionResult.Cancel && result != ScriptExecutionResult.Halted) @@ -116,13 +115,13 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode if (result == ScriptExecutionResult.Cancel) { - RaiseBatchMessage(String.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_QueryCancelledbyUser)); + RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_QueryCancelledbyUser)); } else { if (numBatchExecutionTimes > 1) { - RaiseBatchMessage(String.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_FinalizingLoop, numBatchExecutionTimes)); + RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_FinalizingLoop, numBatchExecutionTimes)); } } } @@ -391,6 +390,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode currentBatch.Text = batchScript; currentBatch.TextSpan = textSpan; currentBatch.BatchIndex = currentBatchIndex; + currentBatch.ExpectedExecutionCount = numBatchExecutionTimes; currentBatchIndex++; @@ -489,7 +489,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode result = ScriptExecutionResult.Failure; string info = ex.Text; - RaiseScriptError(String.Format(CultureInfo.CurrentCulture, SR.EE_ScriptError_ParsingSyntax, info), ScriptMessageType.FatalError); + RaiseScriptError(string.Format(CultureInfo.CurrentCulture, SR.EE_ScriptError_ParsingSyntax, info), ScriptMessageType.FatalError); } } catch (Exception ex) @@ -730,13 +730,13 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode // SET SHOWPLAN_TEXT cannot be used with other statements in the batch preConditionBatches.Insert(0, new Batch( - String.Format(CultureInfo.CurrentCulture, "{0} ", ExecutionEngineConditions.ShowPlanTextStatement(true)), + string.Format(CultureInfo.CurrentCulture, "{0} ", ExecutionEngineConditions.ShowPlanTextStatement(true)), false, executionTimeout)); postConditionBatches.Insert(0, new Batch( - String.Format(CultureInfo.CurrentCulture, "{0} ", ExecutionEngineConditions.ShowPlanTextStatement(false)), + string.Format(CultureInfo.CurrentCulture, "{0} ", ExecutionEngineConditions.ShowPlanTextStatement(false)), false, executionTimeout)); } @@ -938,7 +938,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode { if (isSqlCmdConnection) { - RaiseBatchMessage(String.Format(CultureInfo.CurrentCulture, "Disconnection from server {0}", ReliableConnectionHelper.GetServerName(connection))); + RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, "Disconnection from server {0}", ReliableConnectionHelper.GetServerName(connection))); CloseConnection(connection); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 9cfdb072..90393d42 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -749,11 +749,11 @@ namespace Microsoft.SqlTools.ServiceLayer } } - public static string EE_ExecutionInfo_InitilizingLoop + public static string EE_ExecutionInfo_InitializingLoop { get { - return Keys.GetString(Keys.EE_ExecutionInfo_InitilizingLoop); + return Keys.GetString(Keys.EE_ExecutionInfo_InitializingLoop); } } @@ -3979,7 +3979,7 @@ namespace Microsoft.SqlTools.ServiceLayer public const string EE_BatchExecutionError_Ignoring = "EE_BatchExecutionError_Ignoring"; - public const string EE_ExecutionInfo_InitilizingLoop = "EE_ExecutionInfo_InitilizingLoop"; + public const string EE_ExecutionInfo_InitializingLoop = "EE_ExecutionInfo_InitializingLoop"; public const string EE_ExecutionError_CommandNotSupported = "EE_ExecutionError_CommandNotSupported"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.de.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.de.resx index 92ad1add..ec7f0db9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.de.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.de.resx @@ -91,7 +91,7 @@ Sie haben die Abfrage abgebrochen. Fehler während der Batchausführung. Fehler während der Batchausführung, aber des Fehlers wurde ignoriert. - {0}-malige Batchausführung wurde gestartet. + {0}-malige Batchausführung wurde gestartet. Befehl {0} wird nicht unterstützt. Die Variable {0} konnte nicht gefunden werden. Fehler bei der SQL-Ausführung: {0} @@ -479,4 +479,12 @@ prototype_db_prop_parameterization = Parameterization Der angegebene Dateiname ist zugleich ein Verzeichnisname: {0} Es kann nicht überprüft werden, ob der Speicherort der Sicherungsdatei vorhanden ist: {0} Auf den angegebenen Pfad auf dem Server kann nicht zugegriffen werden: {0} + Kein Sicherungssatz zur Wiederherstellung ausgewählt + Nie + Azure SQL DB + Azure SQL Data Warehouse + Azure SQL Stretch Database + Der Pfad {0} ist kein gültiges Verzeichnis + Die Datei {1} im Verzeichnis {0} existiert bereits. + Der Wert {0} ist zu groß für eine Spalte mit dem Datentyp {1} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.es.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.es.resx index e28c1994..c20052ae 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.es.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.es.resx @@ -91,7 +91,7 @@ Se canceló la consulta. Se produjo un error mientras se ejecutaba el lote. Se produjo un error mientras se ejecutaba el lote, pero se ha omitido el error. - Iniciando bucle de ejecución de {0} veces... + Iniciando bucle de ejecución de {0} veces... No se admite el comando {0}. La variable {0} no se encontró. Error de ejecución de SQL: {0} @@ -478,4 +478,12 @@ El nombre del archivo especificado es un nombre de directorio: {0} No se puede verificar la existencia de la ubicación del archivo de copia de seguridad: {0} No se puede acceder a la ruta de acceso especificada en el servidor: {0} + Ningún backupset seleccionado para ser restaurado + Nunca + Azure SQL Database + Azure SQL Data Warehouse + Azure SQL Stretch Database + La ruta de acceso [{0}] no es un directorio válido + Ya existe un archivo {1} en el directorio '{0}' + El valor {0} es muy grande para el tipo de columna {1} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.fr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.fr.resx index adb397ac..aa697a53 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.fr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.fr.resx @@ -91,7 +91,7 @@ Vous avez annulé la requête. Une erreur s'est produite lors de l'exécution du lot. Une erreur s'est produite lors de l'exécution du lot, mais elle a été ignorée. - Démarrage de la boucle d'exécution pour {0} fois... + Démarrage de la boucle d'exécution pour {0} fois... La commande {0} n'est pas prise en charge. Impossible de trouver la variable {0}. Erreur d’exécution de SQL : {0} @@ -478,4 +478,12 @@ Le nom de fichier spécifié est également un nom de répertoire: {0} Impossible de vérifier l'existence de l'emplacement du fichier de sauvegarde: {0} Impossible d'accéder au chemin d'accès spécifié sur le serveur: {0} + Aucun jeu de sauvegarde n'a été sélectionné pour être restauré. + Jamais + Azure SQL DB + Azure SQL Data Warehouse + Azure SQL Stretch Database + Le chemin {0} n'est pas un répertoire valide. + Pour le répertoire {0} un fichier avec le nom {1} existe déjà + La valeur {0} est trop grande pour tenir dans la colonne de type {1} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.it.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.it.resx index 2712b8c8..8a7b4e9f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.it.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.it.resx @@ -91,7 +91,7 @@ È stata annullata la query. Si è verificato un errore durante l'esecuzione del batch. Si è verificato un errore durante l'esecuzione del batch, ma l'errore è stato ignorato. - Avvio ciclo di esecuzione di {0} volte... + Avvio ciclo di esecuzione di {0} volte... Il comando {0} non è supportato. Impossibile trovare la variabile {0}. Errore di esecuzione di SQL: {0} @@ -478,4 +478,12 @@ Il nome file specificato è anche un nome di cartella: {0} Impossibile verificare l'esistenza della posizione del file di backup: {0} Impossibile accedere al percorso specificato sul server: {0} + Nessun insieme di backup selezionato per il ripristino + Mai + Azure SQL DB + Azure SQL Data Warehouse + Azure SQL Stretch Database + Il percorso [{0}] non è una directory valida. + Nella directory '{0}' esiste già il file {1} + Il valore {0} è troppo grande per essere contenuto in una colonna di tipo {1} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ja.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ja.resx index aee7f384..3c8a8e71 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ja.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ja.resx @@ -91,7 +91,7 @@ クエリをキャンセルしました。 バッチの実行中にエラーが発生しました。 バッチの実行中にエラーが発生しましたが、エラーを無視しました。 - {0} 回の実行ループを開始しています. + {0} 回の実行ループを開始しています. コマンド {0} はサポートされていません。 変数 {0} が見つかりませんでした。 SQL の実行エラー: {0} @@ -451,8 +451,10 @@ Enabledprototype_db_prop_parameterization = Parameterization NORECOVERY オプションを使用してバックアップを復元するときにこのオプションを指定することはできません。 データベース ファイルのパスが無効です: '{0}' ログ + リストア プランの作成に失敗しました。 データベースのリストアはサポートされていません。 データベースのリストア + (コピーのみ) コンポーネント 種類 サーバー @@ -473,7 +475,16 @@ Enabledprototype_db_prop_parameterization = Parameterization 実行中 完了 スクリプト - 指定されたファイル名はディレクトリ名と重複します: {0} - バックアップ ファイルの場所が存在するかどうかを確認できません: {0} + 接続が見つかりません。 + 指定されたファイル名がディレクトリ名と同じです: {0} + バックアップ ファイルの場所が存在するかどうか確認できません: {0} サーバーで指定されたパスにアクセスできません: {0} + リストアするバックアップセットが選択されていません + 行わない + Azure SQL DB + Azure SQL Data Warehouse + Azure SQL Stretch Database + パス {0} は有効なディレクトリではありません + ディレクトリ {0} 内に {1} という名前のファイルは既に存在します + 値 {0} は大きすぎるため、型 {1} の列に収まりません \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ko.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ko.resx index 73cac819..71c1aad6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ko.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ko.resx @@ -91,7 +91,7 @@ 쿼리를 취소 했습니다. 일괄 처리를 실행 하는 동안 오류가 발생 합니다. 일괄 처리를 실행 하는 동안 오류가 발생했으나 그 오류는 무시되었습니다. - {0} 번 루프 실행을 시작 하는 중... + {0} 번 루프 실행을 시작 하는 중... {0} 명령은 지원되지 않습니다. {0} 변수를 찾을 수 없습니다. SQL 실행 오류: {0} @@ -480,4 +480,12 @@ votes 지정한 파일 이름은 디렉터리 이름이기도 합니다: {0} 백업 파일 위치를 확인할 수 없습니다: {0} 서버에서 지정된 경로에 액세스할 수 없습니다: {0} + 복원하려는 백업 세트를 선택하지 않았습니다 + 안 함 + Azure SQL DB + Azure SQL Data Warehouse + Azure SQL 신축성 데이터베이스 + 경로 [{0}]은(는) 올바른 디렉터리가 아닙니다. + 디렉터리 {0}에 대한 파일{1} 이 이미 존재합니다. + 값 {0}이 너무 커서 {1} 유형의 열에 들어갈 수 없습니다. \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.pt-BR.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.pt-BR.resx index 689ad3dc..e61558da 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.pt-BR.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.pt-BR.resx @@ -91,7 +91,7 @@ Você cancelou a consulta. Ocorreu um erro enquanto o lote estava sendo executado. Ocorreu um erro enquanto o lote estava sendo executado, mas o erro foi ignorado. - Iniciando a execução do loop {0} vezes... + Iniciando a execução do loop {0} vezes... Comando {0} não é suportado. A variável {0} não pôde ser encontrada. Erro de execução de SQL: {0} @@ -479,4 +479,12 @@ O nome do arquivo especificado também é um nome de diretório: {0} Não foi possível verificar a existência do local do arquivo de backup: {0} Não foi possível acessar o diretório especificado no servidor: {0} + Nenhum conjunto de backup selecionado para ser restaurado + Nunca + Azure SQL DB + Azure SQL Data Warehouse + Azure SQL Stretch Database + Caminho {0} não é um diretório válido + Já existe um arquivo com nome {1} para o diretório {0} + Valor {0} é muito grande para caber em uma coluna do tipo {1} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 0c7d3c4b..071a693d 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -537,7 +537,7 @@ - Execution completed {0} times... + Batch execution completed {0} times... @@ -552,8 +552,8 @@ An error occurred while the batch was being executed, but the error has been ignored. - - Starting execution loop of {0} times... + + Beginning execution loop diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ru.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ru.resx index fe7a2fad..9618e387 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ru.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.ru.resx @@ -91,7 +91,7 @@ Пользователь отменил запрос. При выполнении пакета произошла ошибка. В процессе выполнения пакета произошла ошибка, но она была проигнорирована. - Начало цикла выполнения {0} раз... + Начало цикла выполнения {0} раз... Команда {0} не поддерживается. Переменная {0} не найдена. Ошибка выполнения SQL: {0} @@ -478,4 +478,6 @@ Указанное имя файла является также именем каталога: {0} Невозможно проверить существование расположения файла резервной копии: {0} Указанный путь на сервере недоступен: {0} + Для восстановления не выбран резервный набор данных + Никогда \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 1d7d1c16..201900f2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -255,7 +255,7 @@ EE_ScriptError_ParsingSyntax = Incorrect syntax was encountered while {0} was be EE_ScriptError_FatalError = A fatal error occurred. -EE_ExecutionInfo_FinalizingLoop = Execution completed {0} times... +EE_ExecutionInfo_FinalizingLoop = Batch execution completed {0} times... EE_ExecutionInfo_QueryCancelledbyUser = You cancelled the query. @@ -263,7 +263,7 @@ EE_BatchExecutionError_Halting = An error occurred while the batch was being exe EE_BatchExecutionError_Ignoring = An error occurred while the batch was being executed, but the error has been ignored. -EE_ExecutionInfo_InitilizingLoop = Starting execution loop of {0} times... +EE_ExecutionInfo_InitializingLoop = Beginning execution loop EE_ExecutionError_CommandNotSupported = Command {0} is not supported. diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 84b7d2c9..e1f2f30c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -315,7 +315,7 @@ - Execution completed {0} times... + Batch execution completed {0} times... Execution completed {0} times... @@ -334,8 +334,8 @@ An error occurred while the batch was being executed, but the error has been ignored. - - Starting execution loop of {0} times... + + Beginning execution loop Starting execution loop of {0} times... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hans.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hans.resx index 4a6ea0f7..048fcfff 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hans.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hans.resx @@ -91,7 +91,7 @@ 您已取消查询。 执行批次处理时发生错误。 执行批次处理时发生错误,但该错误已被忽略。 - 正在开始执行循环的次数为 {0} 次... + 正在开始执行循环的次数为 {0} 次... 不支持命令 {0}。 找不到变量 {0}。 SQL 执行错误︰ {0} @@ -475,7 +475,15 @@ 已完成 开始执行脚本操作 找不到连接 - 指定的文件名也是一个目录名: {0} - 无法验证备份文件位置是否存在: {0} + 所指定的文件名同时也是一个文件目录名: {0} + 无法验证备份文件的位置是否存在: {0} 无法访问服务器上的指定路径: {0} + 未选择用于还原的备份集 + 从不 + Azure SQL 数据库 + Azure SQL 数据仓库 + Azure SQL Stretch Database + 路径 {0} 不是有效的目录 + {0} 文件夹中已存在名为 {1} 的文件 + 值 {0} 太大,无法放入类型为 {1} 的列 \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hant.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hant.resx index 8f518686..9bba3490 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hant.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.zh-hant.resx @@ -91,7 +91,7 @@ 您已取消查詢。 執行此批次時發生錯誤。 執行批次,但被忽略的錯誤時,就會發生錯誤。 - 正在啟動 {0} 次執行迴圈... + 正在啟動 {0} 次執行迴圈... 不支援命令 {0}。 找不到變數 {0}。 SQL 執行錯誤︰ {0} @@ -480,4 +480,12 @@ ANSI Padding 已啟用 指定的檔案名稱也是目錄名稱: {0} 無法確認備份檔案位置的存在: {0} 無法存取伺服器上指定的路徑: {0} + 無選擇的備份集可還原 + 永不 + Azure SQL DB + Azure SQL 資料倉儲 + Azure SQL 延展資料庫 + 路徑 {0} 不是有效的目錄 + 因目錄 {0} 中已有存在的檔案名稱 {1} + 數值 {0} 太大以致於無法符合欄位型態 {1} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.de.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.de.xlf index f7e8da84..2b9f4727 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.de.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.de.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... {0}-malige Batchausführung wurde gestartet. diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.es.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.es.xlf index 957c3bb5..f57215bf 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.es.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.es.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... Iniciando bucle de ejecución de {0} veces... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.fr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.fr.xlf index d35bd1b8..e9525ab1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.fr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.fr.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... Démarrage de la boucle d'exécution pour {0} fois... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.it.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.it.xlf index 37a36896..ac2c4eff 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.it.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.it.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... Avvio ciclo di esecuzione di {0} volte... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ja.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ja.xlf index 56f8a53d..489d7af9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ja.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ja.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... {0} 回の実行ループを開始しています. diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ko.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ko.xlf index 580831fd..64416e53 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ko.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ko.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... {0} 번 루프 실행을 시작 하는 중... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.pt-BR.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.pt-BR.xlf index 9af13add..bd018db5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.pt-BR.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.pt-BR.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... Iniciando a execução do loop {0} vezes... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ru.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ru.xlf index b08980a3..e3ae4879 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ru.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.ru.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... Начало цикла выполнения {0} раз... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hans.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hans.xlf index 6f0b05da..dd1d2264 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hans.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hans.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... 正在开始执行循环的次数为 {0} 次... diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hant.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hant.xlf index b24a32ab..029bb722 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hant.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/transXliff/sr.zh-hant.xlf @@ -397,7 +397,7 @@ - + Starting execution loop of {0} times... 正在啟動 {0} 次執行迴圈... diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs index ccd85d20..778d5ac2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs @@ -15,6 +15,7 @@ using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; using Microsoft.SqlTools.Utility; +using System.Globalization; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { @@ -61,7 +62,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution #endregion - internal Batch(string batchText, SelectionData selection, int ordinalId, IFileStreamFactory outputFileFactory) + internal Batch(string batchText, SelectionData selection, int ordinalId, + IFileStreamFactory outputFileFactory, int executionCount = 1) { // Sanity check for input Validate.IsNotNullOrEmptyString(nameof(batchText), batchText); @@ -77,6 +79,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution resultSets = new List(); this.outputFileFactory = outputFileFactory; specialAction = new SpecialAction(); + BatchExecutionCount = executionCount > 0 ? executionCount : 1; } #region Events @@ -123,6 +126,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// public string BatchText { get; set; } + public int BatchExecutionCount { get; private set; } /// /// Localized timestamp for when the execution completed. /// Stored in UTC ISO 8601 format; should be localized before displaying to any user @@ -237,92 +241,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { await BatchStart(this); } - + try { - // Make sure we haven't cancelled yet - cancellationToken.ThrowIfCancellationRequested(); - - // Register the message listener to *this instance* of the batch - // Note: This is being done to associate messages with batches - ReliableSqlConnection sqlConn = conn as ReliableSqlConnection; - DbCommand dbCommand; - if (sqlConn != null) - { - // Register the message listener to *this instance* of the batch - // Note: This is being done to associate messages with batches - sqlConn.GetUnderlyingConnection().InfoMessage += ServerMessageHandler; - dbCommand = sqlConn.GetUnderlyingConnection().CreateCommand(); - - // Add a handler for when the command completes - SqlCommand sqlCommand = (SqlCommand)dbCommand; - sqlCommand.StatementCompleted += StatementCompletedHandler; - } - else - { - dbCommand = conn.CreateCommand(); - } - - // Make sure we aren't using a ReliableCommad since we do not want automatic retry - Debug.Assert(!(dbCommand is ReliableSqlConnection.ReliableSqlCommand), - "ReliableSqlCommand command should not be used to execute queries"); - - // Create a command that we'll use for executing the query - using (dbCommand) - { - // Make sure that we cancel the command if the cancellation token is cancelled - cancellationToken.Register(() => dbCommand?.Cancel()); - - // Setup the command for executing the batch - dbCommand.CommandText = BatchText; - dbCommand.CommandType = CommandType.Text; - dbCommand.CommandTimeout = 0; - executionStartTime = DateTime.Now; - - // Execute the command to get back a reader - using (DbDataReader reader = await dbCommand.ExecuteReaderAsync(cancellationToken)) - { - int resultSetOrdinal = 0; - do - { - // Verify that the cancellation token hasn't benn cancelled - cancellationToken.ThrowIfCancellationRequested(); - - // Skip this result set if there aren't any rows (ie, UPDATE/DELETE/etc queries) - if (!reader.HasRows && reader.FieldCount == 0) - { - continue; - } - - // This resultset has results (ie, SELECT/etc queries) - ResultSet resultSet = new ResultSet(resultSetOrdinal, Id, outputFileFactory); - resultSet.ResultCompletion += ResultSetCompletion; - - // Add the result set to the results of the query - lock (resultSets) - { - resultSets.Add(resultSet); - resultSetOrdinal++; - } - - // Read until we hit the end of the result set - await resultSet.ReadResultToEnd(reader, cancellationToken); - - } while (await reader.NextResultAsync(cancellationToken)); - - // If there were no messages, for whatever reason (NO COUNT set, messages - // were emitted, records returned), output a "successful" message - if (!messagesSent) - { - await SendMessage(SR.QueryServiceCompletedSuccessfully, false); - } - } - } - } - catch (DbException dbe) - { - HasError = true; - await UnwrapDbException(dbe); + await DoExecute(conn, cancellationToken); } catch (TaskCanceledException) { @@ -355,6 +277,131 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution await BatchCompletion(this); } } + + } + + private async Task DoExecute(DbConnection conn, CancellationToken cancellationToken) + { + bool canContinue = true; + int timesLoop = this.BatchExecutionCount; + + await SendMessageIfExecutingMultipleTimes(SR.EE_ExecutionInfo_InitializingLoop, false); + + while (canContinue && timesLoop > 0) + { + try + { + await ExecuteOnce(conn, cancellationToken); + } + catch (DbException dbe) + { + HasError = true; + canContinue = await UnwrapDbException(dbe); + if (canContinue) + { + // If it's a multi-batch, we notify the user that we're ignoring a single failure. + await SendMessageIfExecutingMultipleTimes(SR.EE_BatchExecutionError_Ignoring, false); + } + } + timesLoop--; + } + + await SendMessageIfExecutingMultipleTimes(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_FinalizingLoop, this.BatchExecutionCount), false); + } + + private async Task SendMessageIfExecutingMultipleTimes(string message, bool isError) + { + if (IsExecutingMultipleTimes()) + { + await SendMessage(message, isError); + } + } + + private bool IsExecutingMultipleTimes() + { + return this.BatchExecutionCount > 1; + } + + private async Task ExecuteOnce(DbConnection conn, CancellationToken cancellationToken) + { + // Make sure we haven't cancelled yet + cancellationToken.ThrowIfCancellationRequested(); + + // Register the message listener to *this instance* of the batch + // Note: This is being done to associate messages with batches + ReliableSqlConnection sqlConn = conn as ReliableSqlConnection; + DbCommand dbCommand; + if (sqlConn != null) + { + // Register the message listener to *this instance* of the batch + // Note: This is being done to associate messages with batches + sqlConn.GetUnderlyingConnection().InfoMessage += ServerMessageHandler; + dbCommand = sqlConn.GetUnderlyingConnection().CreateCommand(); + + // Add a handler for when the command completes + SqlCommand sqlCommand = (SqlCommand)dbCommand; + sqlCommand.StatementCompleted += StatementCompletedHandler; + } + else + { + dbCommand = conn.CreateCommand(); + } + + // Make sure we aren't using a ReliableCommad since we do not want automatic retry + Debug.Assert(!(dbCommand is ReliableSqlConnection.ReliableSqlCommand), + "ReliableSqlCommand command should not be used to execute queries"); + + // Create a command that we'll use for executing the query + using (dbCommand) + { + // Make sure that we cancel the command if the cancellation token is cancelled + cancellationToken.Register(() => dbCommand?.Cancel()); + + // Setup the command for executing the batch + dbCommand.CommandText = BatchText; + dbCommand.CommandType = CommandType.Text; + dbCommand.CommandTimeout = 0; + executionStartTime = DateTime.Now; + + // Execute the command to get back a reader + using (DbDataReader reader = await dbCommand.ExecuteReaderAsync(cancellationToken)) + { + int resultSetOrdinal = 0; + do + { + // Verify that the cancellation token hasn't benn cancelled + cancellationToken.ThrowIfCancellationRequested(); + + // Skip this result set if there aren't any rows (ie, UPDATE/DELETE/etc queries) + if (!reader.HasRows && reader.FieldCount == 0) + { + continue; + } + + // This resultset has results (ie, SELECT/etc queries) + ResultSet resultSet = new ResultSet(resultSetOrdinal, Id, outputFileFactory); + resultSet.ResultCompletion += ResultSetCompletion; + + // Add the result set to the results of the query + lock (resultSets) + { + resultSets.Add(resultSet); + resultSetOrdinal++; + } + + // Read until we hit the end of the result set + await resultSet.ReadResultToEnd(reader, cancellationToken); + + } while (await reader.NextResultAsync(cancellationToken)); + + // If there were no messages, for whatever reason (NO COUNT set, messages + // were emitted, records returned), output a "successful" message + if (!messagesSent) + { + await SendMessage(SR.QueryServiceCompletedSuccessfully, false); + } + } + } } /// @@ -487,8 +534,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// converted to SqlException, the message is written to the messages list. /// /// The exception to unwrap - private async Task UnwrapDbException(Exception dbe) + /// true is exception can be ignored when in a loop, false otherwise + private async Task UnwrapDbException(Exception dbe) { + bool canIgnore = true; SqlException se = dbe as SqlException; if (se != null) { @@ -499,6 +548,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { // User cancellation error, add the single message await SendMessage(SR.QueryServiceQueryCancelled, false); + canIgnore = false; } else { @@ -517,6 +567,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { await SendMessage(dbe.Message, true); } + return canIgnore; } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs index 46ab9d41..37c81374 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs @@ -118,7 +118,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution batchDefinition.StartColumn-1, batchDefinition.EndLine-1, batchDefinition.EndColumn-1), - index, outputFactory)); + index, outputFactory, + batchDefinition.BatchExecutionCount)); Batches = batchSelection.ToArray(); @@ -462,7 +463,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// private static void AddBatch(string query, ICollection batchSet, IFileStreamFactory outputFactory) { - batchSet.Add(new Batch(query, null, batchSet.Count, outputFactory)); + batchSet.Add(new Batch(query, null, batchSet.Count, outputFactory, 1)); } #endregion diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/BatchParser/BatchParserWrapperTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/BatchParser/BatchParserWrapperTests.cs index 4edf900a..51ac76d0 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/BatchParser/BatchParserWrapperTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/BatchParser/BatchParserWrapperTests.cs @@ -1,4 +1,5 @@ -using Microsoft.SqlTools.ServiceLayer.BatchParser; +using System; +using Microsoft.SqlTools.ServiceLayer.BatchParser; using Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode; using Xunit; @@ -20,6 +21,20 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.BatchParser Assert.Equal(1, batch.StartColumn); Assert.Equal(2, batch.EndLine); Assert.Equal(sqlScript.Length+1, batch.EndColumn); + Assert.Equal(1, batch.BatchExecutionCount); + } + } + + [Fact] + public void CheckSQLBatchStatementWithRepeatExecution() + { + using (BatchParserWrapper parserWrapper = new BatchParserWrapper()) + { + string sqlScript = "select * from sys.object" + Environment.NewLine + "GO 2"; + var batches = parserWrapper.GetBatches(sqlScript); + Assert.Equal(1, batches.Count); + BatchDefinition batch = batches[0]; + Assert.Equal(2, batch.BatchExecutionCount); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/BatchTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/BatchTests.cs index 4a756edd..a4a6ac2d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/BatchTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/BatchTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Connection; @@ -157,6 +158,43 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution ValidateMessages(batch, 1, messages); } + [Fact] + public async Task BatchExecuteMultiExecutions() + { + // Setup: + // ... Keep track of callbacks being called + int batchStartCalls = 0; + int batchEndCalls = 0; + int resultSetCalls = 0; + List messages = new List(); + + // ... Build a data set to return + const int resultSets = 1; + ConnectionInfo ci = Common.CreateTestConnectionInfo(Common.GetTestDataSet(resultSets), false, false); + + // If I execute a query that should get one result set, but execute it twice using "GO 2" syntax + var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory(); + Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory, 2); + BatchCallbackHelper(batch, + b => batchStartCalls++, + b => batchEndCalls++, + m => messages.Add(m), + r => resultSetCalls++); + await batch.Execute(GetConnection(ci), CancellationToken.None); + + // Then: + // ... Callbacks should have been called the appropriate number of times + Assert.Equal(1, batchStartCalls); + Assert.Equal(1, batchEndCalls); + Assert.Equal(2, resultSetCalls); + + // ... There should be exactly two result sets + ValidateBatch(batch, 2, false); + ValidateBatchSummary(batch); + // ... And there should be an additional loop start in addition to the batch end message + ValidateMessages(batch, 2, messages); + } + [Fact] public async Task BatchExecuteInvalidQuery() { @@ -195,6 +233,44 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution }); } + [Fact] + public async Task BatchExecuteInvalidQueryMultiExecutions() + { + // Setup: + // ... Keep track of callbacks being called + int batchStartCalls = 0; + int batchEndCalls = 0; + List messages = new List(); + + // If I execute a batch that is invalid, and if "GO 2" is added to execute more than once + var ci = Common.CreateTestConnectionInfo(null, true, false); + var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory(); + Batch batch = new Batch(Constants.StandardQuery + Environment.NewLine, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory, 2); + BatchCallbackHelper(batch, + b => batchStartCalls++, + b => batchEndCalls++, + m => messages.Add(m), + r => { throw new Exception("ResultSet callback was called when it should not have been."); }); + await batch.Execute(GetConnection(ci), CancellationToken.None); + + // Then: + // ... Callbacks should have been called the appropriate number of times + Assert.Equal(1, batchStartCalls); + Assert.Equal(1, batchEndCalls); + + // ... It should have executed with error + ValidateBatch(batch, 0, true); + ValidateBatchSummary(batch); + + // ... There should be two error messages returned and 4 info messages (loop start/end, plus 2 for ignoring the error) + Assert.Equal(6, messages.Count); + Assert.All(messages, m => + { + Assert.Equal(batch.Id, m.BatchId); + }); + Assert.Equal(2, messages.Where(m => m.IsError).Count()); + } + [Fact] public async Task BatchExecuteExecuted() { diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/SrTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/SrTests.cs index 996aae99..224b75ac 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/SrTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/SrTests.cs @@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility Assert.NotNull(ServiceLayerSr.EE_ExecutionError_CommandNotSupported); Assert.NotNull(ServiceLayerSr.EE_ExecutionError_VariableNotFound); Assert.NotNull(ServiceLayerSr.EE_ExecutionInfo_FinalizingLoop); - Assert.NotNull(ServiceLayerSr.EE_ExecutionInfo_InitilizingLoop); + Assert.NotNull(ServiceLayerSr.EE_ExecutionInfo_InitializingLoop); Assert.NotNull(ServiceLayerSr.EE_ExecutionInfo_QueryCancelledbyUser); Assert.NotNull(ServiceLayerSr.EE_ExecutionNotYetCompleteError); Assert.NotNull(ServiceLayerSr.EE_ScriptError_Error);