Move managed parser into its own project (test code coverage) (#774)

* Created New ManagedBatchParser project in .NetStandard

* Addressing PR Comments

* Resolve 'No Repository' warning.

* Move batch parser tests to integrations test project

* Fix SLN file
This commit is contained in:
Karl Burtram
2019-02-07 20:13:03 -08:00
committed by GitHub
parent 0a172f3c8e
commit 022282800a
92 changed files with 2471 additions and 6391 deletions

View File

@@ -0,0 +1,13 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public enum BatchParserAction
{
Continue = 0,
Abort = 1
}
}

View File

@@ -0,0 +1,48 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public sealed class BatchParserException : Exception
{
const string ErrorCodeName = "ErrorCode";
const string BeginName = "Begin";
const string EndName = "End";
const string TextName = "Text";
const string TokenTypeName = "TokenType";
ErrorCode errorCode;
PositionStruct begin;
PositionStruct end;
string text;
LexerTokenType tokenType;
/// <summary>
/// Class for a custom exception for the Batch Parser
/// </summary>
public BatchParserException(ErrorCode errorCode, Token token, string message)
: base(message)
{
this.errorCode = errorCode;
begin = token.Begin;
end = token.End;
text = token.Text;
tokenType = token.TokenType;
}
public ErrorCode ErrorCode { get { return errorCode; } }
public PositionStruct Begin { get { return begin; } }
public PositionStruct End { get { return end; } }
public string Text { get { return text; } }
public LexerTokenType TokenType { get { return tokenType; } }
}
}

View File

@@ -0,0 +1,491 @@
//
// 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.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode;
using System.Globalization;
using System.Diagnostics;
using Microsoft.SqlTools.ManagedBatchParser;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
/// <summary>
/// Wraps the SMO Batch parser to make it a easily useable component.
/// </summary>
public sealed class BatchParserWrapper : IDisposable
{
private List<BatchInfo> batchInfos;
private ExecutionEngine executionEngine;
private BatchEventNotificationHandler notificationHandler;
/// <summary>
/// Helper method used to Convert line/column information in a file to offset
/// </summary>
private static List<BatchDefinition> ConvertToBatchDefinitionList(string content, List<BatchInfo> batchInfos)
{
List<BatchDefinition> batchDefinitionList = new List<BatchDefinition>();
if (batchInfos.Count == 0)
{
return batchDefinitionList;
}
List<int> offsets = GetOffsets(content, batchInfos);
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 = batchInfos[0].startLine + 1; //positions is 0 index based
int endLine = startLine;
int lineDifference = 0;
int endColumn;
int offset = offsets[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 = batchInfos[1].startLine - batchInfos[0].startLine;
}
// get endLine, endColumn for the current batch and the lineStartOffset for the next batch
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(
batchText,
startLine,
endLine,
startColumn + 1,
endColumn + 1,
batchInfos[0].executionCount
);
batchDefinitionList.Add(batchDef);
if (count > 1)
{
offset = offsets[1] + batchInfos[0].startColumn;
}
// Generate the rest batch definitions
for (int index = 1; index < count - 1; index++)
{
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, batchInfos[index].length);
startLine = batchInfos[index].startLine;
startColumn = batchInfos[index].startColumn;
// make a new batch definition for each batch
BatchDefinition batch = new BatchDefinition(
batchText,
startLine,
endLine,
startColumn + 1,
endColumn + 1,
batchInfos[index].executionCount
);
batchDefinitionList.Add(batch);
}
// if there is only one batch then that was the last one anyway
if (count > 1)
{
batchText = content.Substring(offsets[count-1], batchInfos[count - 1].length);
BatchDefinition lastBatchDef = GetLastBatchDefinition(reader, batchInfos[count - 1], batchText);
batchDefinitionList.Add(lastBatchDef);
}
}
}
return batchDefinitionList;
}
private static int GetMaxStartLine(IList<BatchInfo> batchInfos)
{
int highest = 0;
foreach (var batchInfo in batchInfos)
{
if (batchInfo.startLine > highest)
{
highest = batchInfo.startLine;
}
}
return highest;
}
/// <summary>
/// Gets offsets for all batches
/// </summary>
private static List<int> GetOffsets(string content, IList<BatchInfo> batchInfos)
{
List<int> offsets = new List<int>();
int count = 0;
int offset = 0;
bool foundAllOffsets = false;
int maxStartLine = GetMaxStartLine(batchInfos);
using (StringReader reader = new StringReader(content))
{
// go until we have found offsets for all batches
while (!foundAllOffsets)
{
// go until the last start line of the batches
for (int i = 0; i <= maxStartLine ; i++)
{
// get offset for the current batch
ReadLines(reader, ref count, ref offset, ref foundAllOffsets, batchInfos, offsets, i);
// if we found all the offsets, then we're done
if (foundAllOffsets)
{
break;
}
}
}
}
return offsets;
}
/// <summary>
/// Helper function to read lines of batches to get offsets
/// </summary>
private static void ReadLines(StringReader reader, ref int count, ref int offset, ref bool foundAllOffsets,
IList<BatchInfo> batchInfos, List<int> offsets, int iteration)
{
int ch;
while (true)
{
if (batchInfos[count].startLine == iteration)
{
count++;
offsets.Add(offset);
if (count == batchInfos.Count)
{
foundAllOffsets = true;
break;
}
}
ch = reader.Read();
if (ch == -1) // EOF do nothing
{
break;
}
else if (ch == 10 /* for \n */) // End of line increase and break
{
offset++;
break;
}
else // regular char just increase
{
offset++;
}
}
}
/// <summary>
/// Helper method to get the last batch
/// </summary>
private static BatchDefinition GetLastBatchDefinition(StringReader reader,
BatchInfo batchInfo, string batchText)
{
int startLine = batchInfo.startLine;
int startColumn = batchInfo.startColumn;
string prevLine = null;
string line = reader.ReadLine();
int endLine = startLine;
// find end line
while (line != null)
{
endLine++;
if (line != "\n")
{
prevLine = line;
}
line = reader.ReadLine();
}
// get number of characters in the last line
int endColumn = prevLine.ToCharArray().Length;
return new BatchDefinition(
batchText,
startLine,
endLine,
startColumn + 1,
endColumn + 1,
batchInfo.executionCount
);
}
/// <summary>
/// Helper function to get correct lines and columns
/// in a single batch with multiple statements
/// </summary>
private static Tuple<int, int> GetBatchPositionDetails(StringReader reader, int endLine)
{
string prevLine = null;
string line = reader.ReadLine();
// find end line
while (line != null)
{
endLine++;
if (line != "\n")
{
prevLine = line;
}
line = reader.ReadLine();
}
// get number of characters in the last line
int endColumn = prevLine.ToCharArray().Length;
//lineOffset doesn't matter because its the last batch
return Tuple.Create(endLine, endColumn);
}
/// <summary>
/// Get end line and end column
/// </summary>
private static Tuple<int, int> ReadLines(StringReader reader, int n, int endLine)
{
Validate.IsNotNull(nameof(reader), reader);
int endColumn = 0;
// if only one batch with multiple lines
if (n == 0)
{
return GetBatchPositionDetails(reader, endLine);
}
// if there are more than one batch
for (int i = 0; i < n; i++)
{
int ch;
while (true)
{
ch = reader.Read();
if (ch == -1) // EOF do nothing
{
break;
}
else if (ch == 10 /* for \n */) // End of line increase and break
{
++endLine;
endColumn = 0;
break;
}
else // regular char just increase
{
++endColumn;
}
}
}
return Tuple.Create(endLine, endColumn);
}
/// <summary>
/// Wrapper API for the Batch Parser that returns a list of
/// BatchDefinitions when given a string to parse
/// </summary>
public BatchParserWrapper()
{
executionEngine = new ExecutionEngine();
// subscribe to executionEngine BatchParser events
executionEngine.BatchParserExecutionError += OnBatchParserExecutionError;
executionEngine.BatchParserExecutionFinished += OnBatchParserExecutionFinished;
// instantiate notificationHandler class
notificationHandler = new BatchEventNotificationHandler();
}
/// <summary>
/// Takes in a query string and returns a list of BatchDefinitions
/// </summary>
public List<BatchDefinition> GetBatches(string sqlScript)
{
batchInfos = new List<BatchInfo>();
// execute the script - all communication / integration after here happen via event handlers
executionEngine.ParseScript(sqlScript, notificationHandler);
// retrieve a list of BatchDefinitions
List<BatchDefinition> batchDefinitionList = ConvertToBatchDefinitionList(sqlScript, batchInfos);
return batchDefinitionList;
}
#region ExecutionEngine Event Handlers
private void OnBatchParserExecutionError(object sender, BatchParserExecutionErrorEventArgs args)
{
if (args != null)
{
Logger.Write(TraceEventType.Verbose, SR.BatchParserWrapperExecutionError);
throw new Exception(string.Format(CultureInfo.CurrentCulture,
SR.BatchParserWrapperExecutionEngineError, args.Message + Environment.NewLine + '\t' + args.Description));
}
}
private void OnBatchParserExecutionFinished(object sender, BatchParserExecutionFinishedEventArgs args)
{
try
{
if (args != null && args.Batch != null)
{
// PS168371
//
// There is a bug in the batch parser where it appends a '\n' to the end of the last
// batch if a GO or statement appears at the end of the string without a \r\n. This is
// throwing off length calculations in other places in the code because the length of
// the string returned is longer than the length of the actual string
//
// To work around this issue we detect this case (a single \n without a preceding \r
// and then adjust the length accordingly
string batchText = args.Batch.Text;
int batchTextLength = batchText.Length;
if (!batchText.EndsWith(Environment.NewLine, StringComparison.Ordinal)
&& batchText.EndsWith("\n", StringComparison.Ordinal))
{
batchTextLength -= 1;
}
// Add the script info
batchInfos.Add(new BatchInfo(args.Batch.TextSpan.iStartLine, args.Batch.TextSpan.iStartIndex, batchTextLength, args.Batch.ExpectedExecutionCount));
}
}
catch (NotImplementedException)
{
// intentionally swallow
}
catch (Exception e)
{
// adding this for debugging
Logger.Write(TraceEventType.Warning, "Exception Caught in BatchParserWrapper.OnBatchParserExecutionFinished(...)" + e.ToString());
throw;
}
}
#endregion
#region Internal BatchEventHandlers class
/// <summary>
/// Internal implementation class to implement IBatchEventHandlers
/// </summary>
internal class BatchEventNotificationHandler : IBatchEventsHandler
{
public void OnBatchError(object sender, BatchErrorEventArgs args)
{
if (args != null)
{
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineError);
throw new Exception(SR.BatchParserWrapperExecutionEngineError);
}
}
public void OnBatchMessage(object sender, BatchMessageEventArgs args)
{
#if DEBUG
if (args != null)
{
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchMessage);
}
#endif
}
public void OnBatchResultSetProcessing(object sender, BatchResultSetEventArgs args)
{
#if DEBUG
if (args != null && args.DataReader != null)
{
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchResultSetProcessing);
}
#endif
}
public void OnBatchResultSetFinished(object sender, EventArgs args)
{
#if DEBUG
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchResultSetFinished);
#endif
}
public void OnBatchCancelling(object sender, EventArgs args)
{
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchCancelling);
}
}
#endregion
#region IDisposable implementation
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (disposing)
{
if (executionEngine != null)
{
executionEngine.Dispose();
executionEngine = 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;
}
}
}

View File

@@ -0,0 +1,19 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
/// <summary>This exception is used to report that can come from the Batch Parser inside BatchParserWrapper.</summary>
internal sealed class BatchParserWrapperException : Exception
{
/// <summary>
/// Get description of the BatchParserWrapperException
/// </summary>
public string Description { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public enum ErrorCode
{
ErrorCodeBase = 0,
Success = ErrorCodeBase,
// Lexer error codes
UnsupportedCommand = ErrorCodeBase + 1,
UnrecognizedToken = ErrorCodeBase + 2,
StringNotTerminated = ErrorCodeBase + 3,
CommentNotTerminated = ErrorCodeBase + 4,
// Parser error codes
InvalidVariableName = ErrorCodeBase + 6,
InvalidNumber = ErrorCodeBase + 7,
TokenExpected = ErrorCodeBase + 8,
Aborted = ErrorCodeBase + 9,
CircularReference = ErrorCodeBase + 10,
VariableNotDefined = ErrorCodeBase + 11,
}
}

View File

@@ -0,0 +1,892 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.Utility;
using Microsoft.SqlTools.ManagedBatchParser;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Single batch of SQL command
/// </summary>
public class Batch
{
#region Private methods
/// <summary>
/// Helper method to format the provided SqlError
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
private string FormatSqlErrorMessage(SqlError error)
{
string detailedMessage = string.Empty;
if (error.Class > 10)
{
if (string.IsNullOrEmpty(error.Procedure))
{
detailedMessage = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchSqlMessageNoProcedureInfo,
error.Number,
error.Class,
error.State,
error.LineNumber);
}
else
{
detailedMessage = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchSqlMessageWithProcedureInfo,
error.Number,
error.Class,
error.State,
error.Procedure,
error.LineNumber);
}
}
else if (error.Class > 0 && error.Number > 0)
{
detailedMessage = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchSqlMessageNoLineInfo,
error.Number,
error.Class,
error.State);
}
if (!string.IsNullOrEmpty(detailedMessage) && !isSuppressProviderMessageHeaders)
{
detailedMessage = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", error.Source, detailedMessage);
}
return detailedMessage;
}
/// <summary>
/// Handles a Sql exception
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private ScriptExecutionResult HandleSqlException(SqlException ex)
{
ScriptExecutionResult result;
lock (this)
{
if (state == BatchState.Cancelling)
{
result = ScriptExecutionResult.Cancel;
}
else
{
result = ScriptExecutionResult.Failure;
}
}
if (result != ScriptExecutionResult.Cancel)
{
HandleSqlMessages(ex.Errors);
}
return result;
}
/// <summary>
/// Called when an error message came from SqlClient
/// </summary>
/// <param name="message"></param>
/// <param name="description"></param>
/// <param name="line"></param>
/// <param name="textSpan"></param>
private void RaiseBatchError(string message, SqlError error, TextSpan textSpan)
{
BatchErrorEventArgs args = new BatchErrorEventArgs(message, error, textSpan, null);
RaiseBatchError(args);
}
/// <summary>
/// Called when an error message came from SqlClient
/// </summary>
/// <param name="e"></param>
private void RaiseBatchError(BatchErrorEventArgs e)
{
EventHandler<BatchErrorEventArgs> cache = BatchError;
if (cache != null)
{
cache(this, e);
}
}
/// <summary>
/// Called when a message came from SqlClient
/// </summary>
/// <remarks>
/// Additionally, it's being used to notify the user that the script execution
/// has been finished.
/// </remarks>
/// <param name="detailedMessage"></param>
/// <param name="message"></param>
private void RaiseBatchMessage(string detailedMessage, string message, SqlError error)
{
EventHandler<BatchMessageEventArgs> cache = BatchMessage;
if (cache != null)
{
BatchMessageEventArgs args = new BatchMessageEventArgs(detailedMessage, message, error);
cache(this, args);
}
}
/// <summary>
/// Called when a new result set has to be processed
/// </summary>
/// <param name="resultSet"></param>
private void RaiseBatchResultSetProcessing(IDataReader dataReader, ShowPlanType expectedShowPlan)
{
EventHandler<BatchResultSetEventArgs> cache = BatchResultSetProcessing;
if (cache != null)
{
BatchResultSetEventArgs args = new BatchResultSetEventArgs(dataReader, expectedShowPlan);
BatchResultSetProcessing(this, args);
}
}
/// <summary>
/// Called when the result set has been processed
/// </summary>
private void RaiseBatchResultSetFinished()
{
EventHandler<EventArgs> cache = BatchResultSetFinished;
if (cache != null)
{
cache(this, EventArgs.Empty);
}
}
/// <summary>
/// Called when the batch is being cancelled with an active result set
/// </summary>
private void RaiseCancelling()
{
EventHandler<EventArgs> cache = BatchCancelling;
if (cache != null)
{
cache(this, EventArgs.Empty);
}
}
#endregion
#region Private enums
private enum BatchState
{
Initial,
Executing,
Executed,
ProcessingResults,
Cancelling,
}
#endregion
#region Private fields
// correspond to public properties
private bool isSuppressProviderMessageHeaders;
private bool isResultExpected = false;
private string sqlText = string.Empty;
private int execTimeout = 30;
private int scriptTrackingId = 0;
private bool isScriptExecutionTracked = false;
private const int ChangeDatabase = 0x1645;
//command that will be used for execution
private IDbCommand command = null;
//current object state
private BatchState state = BatchState.Initial;
//script text to be executed
private TextSpan textSpan;
//index of the batch in collection of batches
private int index = 0;
private int expectedExecutionCount = 1;
private long totalAffectedRows = 0;
private bool hasErrors;
// Expected showplan if any
private ShowPlanType expectedShowPlan;
#endregion
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public Batch()
{
// nothing
}
/// <summary>
/// Creates and initializes a batch object
/// </summary>
/// <param name="isResultExpected">Whether it is one of "set [something] on/off" type of command,
/// that doesn't return any results from the server
/// </param>
/// <param name="sqlText">Text of the batch</param>
/// <param name="execTimeout">Timeout for the batch execution. 0 means no limit </param>
public Batch(string sqlText, bool isResultExpected, int execTimeout)
{
this.isResultExpected = isResultExpected;
this.sqlText = sqlText;
this.execTimeout = execTimeout;
}
#endregion
#region Public properties
/// <summary>
/// Is the Batch's text valid?
/// </summary>
public bool HasValidText
{
get
{
return !string.IsNullOrEmpty(sqlText);
}
}
/// <summary>
/// SQL text that to be executed in the Batch
/// </summary>
public string Text
{
get
{
return sqlText;
}
set
{
sqlText = value;
}
}
/// <summary>
/// Determines whether batch execution returns any results
/// </summary>
public bool IsResultsExpected
{
get
{
return isResultExpected;
}
set
{
isResultExpected = value;
}
}
/// <summary>
/// Determines the execution timeout for the batch
/// </summary>
public int ExecutionTimeout
{
get
{
return execTimeout;
}
set
{
execTimeout = value;
}
}
/// <summary>
/// Determines the textspan to wich the batch belongs to
/// </summary>
public TextSpan TextSpan
{
get
{
return textSpan;
}
set
{
textSpan = value;
}
}
/// <summary>
/// Determines the batch index in the collection of batches being executed
/// </summary>
public int BatchIndex
{
get
{
return index;
}
set
{
index = value;
}
}
/// <summary>
/// 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
/// </summary>
public int ExpectedExecutionCount
{
get
{
return expectedExecutionCount;
}
set
{
expectedExecutionCount = value;
}
}
/// <summary>
/// Returns how many rows were affected. It should be the value that can be shown
/// in the UI.
/// </summary>
/// <remarks>
/// It can be used only after the execution of the batch is finished
/// </remarks>
public long RowsAffected
{
get
{
return totalAffectedRows;
}
}
/// <summary>
/// Determines if the error.Source should be used when messages are written
/// </summary>
public bool IsSuppressProviderMessageHeaders
{
get
{
return isSuppressProviderMessageHeaders;
}
set
{
isSuppressProviderMessageHeaders = value;
}
}
/// <summary>
/// Gets or sets the id of the script we are tracking
/// </summary>
public int ScriptTrackingId
{
get
{
return scriptTrackingId;
}
set
{
scriptTrackingId = value;
}
}
#endregion
#region Public events
/// <summary>
/// fired when there is an error message from the server
/// </summary>
public event EventHandler<BatchErrorEventArgs> BatchError = null;
/// <summary>
/// fired when there is a message from the server
/// </summary>
public event EventHandler<BatchMessageEventArgs> BatchMessage = null;
/// <summary>
/// fired when there is a new result set available. It is guarnteed
/// to be fired from the same thread that called Execute method
/// </summary>
public event EventHandler<BatchResultSetEventArgs> BatchResultSetProcessing = null;
/// <summary>
/// fired when the batch recieved cancel request BEFORE it
/// initiates cancel operation. Note that it is fired from a
/// different thread then the one used to kick off execution
/// </summary>
public event EventHandler<EventArgs> BatchCancelling = null;
/// <summary>
/// fired when we've done absolutely all actions for the current result set
/// </summary>
public event EventHandler<EventArgs> BatchResultSetFinished = null;
#endregion
#region Public methods
/// <summary>
/// Resets the object to its initial state
/// </summary>
public void Reset()
{
lock (this)
{
state = BatchState.Initial;
command = null;
textSpan = new TextSpan();
totalAffectedRows = 0;
hasErrors = false;
expectedShowPlan = ShowPlanType.None;
isSuppressProviderMessageHeaders = false;
scriptTrackingId = 0;
isScriptExecutionTracked = false;
}
}
/// <summary>
/// Executes the batch
/// </summary>
/// <param name="connection">Connection to use</param>
/// <param name="expectedShowPlan">ShowPlan type to be used</param>
/// <returns>result of execution</returns>
/// <remarks>
/// It does not return until execution is finished
/// We may have received a Cancel request by the time this function is called
/// </remarks>
public ScriptExecutionResult Execute(SqlConnection connection, ShowPlanType expectedShowPlan)
{
// FUTURE CLEANUP: Remove in favor of general signature (IDbConnection) - #920978
return Execute((IDbConnection)connection, expectedShowPlan);
}
/// <summary>
/// Executes the batch
/// </summary>
/// <param name="connection">Connection to use</param>
/// <param name="expectedShowPlan">ShowPlan type to be used</param>
/// <returns>result of execution</returns>
/// <remarks>
/// It does not return until execution is finished
/// We may have received a Cancel request by the time this function is called
/// </remarks>
public ScriptExecutionResult Execute(IDbConnection connection, ShowPlanType expectedShowPlan)
{
Validate.IsNotNull(nameof(connection), connection);
//makes sure that the batch is not in use
lock (this)
{
Debug.Assert(command == null, "SQLCommand is NOT null");
if (command != null)
{
command = null;
}
}
this.expectedShowPlan = expectedShowPlan;
return DoBatchExecutionImpl(connection, sqlText);
}
/// <summary>
/// Cancels the batch
/// </summary>
/// <remarks>
/// When batch is actually cancelled, Execute() will return with the appropiate status
/// </remarks>
public void Cancel()
{
lock (this)
{
if (state != BatchState.Cancelling)
{
state = BatchState.Cancelling;
RaiseCancelling();
if (command != null)
{
try
{
command.Cancel();
Debug.WriteLine("Batch.Cancel: command.Cancel completed");
}
catch (SqlException)
{
// eat it
}
catch (RetryLimitExceededException)
{
// eat it
}
}
}
}
}
#endregion
#region Protected methods
/// <summary>
/// Fires an error message event
/// </summary>
/// <param name="ex">Exception caught</param>
/// <remarks>
/// Non-SQL exception
/// </remarks>
protected void HandleExceptionMessage(Exception ex)
{
BatchErrorEventArgs args = new BatchErrorEventArgs(string.Format(CultureInfo.CurrentCulture, SR.EE_BatchError_Exception, ex.Message), ex);
RaiseBatchError(args);
}
/// <summary>
/// Fires a message event
/// </summary>
/// <param name="errors">SqlClient errors collection</param>
/// <remarks>
/// Sql specific messages.
/// </remarks>
protected void HandleSqlMessages(SqlErrorCollection errors)
{
foreach (SqlError error in errors)
{
if (error.Number == ChangeDatabase)
{
continue;
}
string detailedMessage = FormatSqlErrorMessage(error);
if (error.Class > 10)
{
// expose this event as error
Debug.Assert(detailedMessage.Length != 0);
RaiseBatchError(detailedMessage, error, textSpan);
//at least one error message has been used
hasErrors = true;
}
else
{
RaiseBatchMessage(detailedMessage, error.Message, error);
}
}
}
/// <summary>
/// method that will be passed as delegate to SqlConnection.InfoMessage
/// </summary>
protected void OnSqlInfoMessageCallback(object sender, SqlInfoMessageEventArgs e)
{
HandleSqlMessages(e.Errors);
}
/// <summary>
/// Delegete for SqlCommand.RecordsAffected
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks>
/// This is exposed as a regular message
/// </remarks>
protected void OnStatementExecutionFinished(object sender, StatementCompletedEventArgs e)
{
string message = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchExecutionInfo_RowsAffected,
e.RecordCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
RaiseBatchMessage(message, message, null);
}
/// <summary>
/// Called on a new ResultSet on the data reader
/// </summary>
/// <param name="dataReader">True if result set consumed, false on a Cancel request</param>
/// <returns></returns>
/// <remarks>
/// The GridStorageResultSet created is owned by the batch consumer. It's only created here.
/// Additionally, when BatchResultSet event handler is called, it won't return until
/// all data is prcessed or the data being processed is terminated (i.e. cancel or error)
/// </remarks>
protected ScriptExecutionResult ProcessResultSet(IDataReader dataReader)
{
if (dataReader == null)
{
throw new ArgumentNullException();
}
Debug.WriteLine("ProcessResultSet: result set has been created");
//initialize result variable that will be set by batch consumer
ScriptExecutionResult scriptExecutionResult = ScriptExecutionResult.Success;
RaiseBatchResultSetProcessing(dataReader, expectedShowPlan);
if (state != BatchState.Cancelling)
{
return scriptExecutionResult;
}
else
{
return ScriptExecutionResult.Cancel;
}
}
// FUTURE CLEANUP: Remove in favor of general signature (IDbConnection) - #920978
protected ScriptExecutionResult DoBatchExecution(SqlConnection connection, string script)
{
return DoBatchExecutionImpl(connection, script);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities"), SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
[SuppressMessage("Microsoft.Usage", "CA2219:DoNotRaiseExceptionsInExceptionClauses")]
private ScriptExecutionResult DoBatchExecutionImpl(IDbConnection connection, string script)
{
Validate.IsNotNull(nameof(connection), connection);
lock (this)
{
if (state == BatchState.Cancelling)
{
state = BatchState.Initial;
return ScriptExecutionResult.Cancel;
}
}
ScriptExecutionResult result = ScriptExecutionResult.Success;
// SqlClient event handlers setup
SqlInfoMessageEventHandler messageHandler = new SqlInfoMessageEventHandler(OnSqlInfoMessageCallback);
StatementCompletedEventHandler statementCompletedHandler = null;
DbConnectionWrapper connectionWrapper = new DbConnectionWrapper(connection);
connectionWrapper.InfoMessage += messageHandler;
IDbCommand command = connection.CreateCommand();
command.CommandText = script;
command.CommandTimeout = execTimeout;
DbCommandWrapper commandWrapper = null;
if (isScriptExecutionTracked && DbCommandWrapper.IsSupportedCommand(command))
{
statementCompletedHandler = new StatementCompletedEventHandler(OnStatementExecutionFinished);
commandWrapper = new DbCommandWrapper(command);
commandWrapper.StatementCompleted += statementCompletedHandler;
}
lock (this)
{
state = BatchState.Executing;
this.command = command;
command = null;
}
try
{
result = this.ExecuteCommand();
}
catch (OutOfMemoryException)
{
throw;
}
catch (SqlException sqlEx)
{
result = HandleSqlException(sqlEx);
}
catch (Exception ex)
{
result = ScriptExecutionResult.Failure;
HandleExceptionMessage(ex);
}
finally
{
if (messageHandler == null)
{
Logger.Write(TraceEventType.Error, "Expected handler to be declared");
}
if (null != connectionWrapper)
{
connectionWrapper.InfoMessage -= messageHandler;
}
if (commandWrapper != null)
{
if (statementCompletedHandler == null)
{
Logger.Write(TraceEventType.Error, "Expect handler to be declared if we have a command wrapper");
}
commandWrapper.StatementCompleted -= statementCompletedHandler;
}
lock (this)
{
state = BatchState.Initial;
if (command != null)
{
command.Dispose();
command = null;
}
}
}
return result;
}
private ScriptExecutionResult ExecuteCommand()
{
if (command == null)
{
throw new ArgumentNullException("command");
}
return this.ExecuteUnTrackedCommand();
}
private ScriptExecutionResult ExecuteUnTrackedCommand()
{
IDataReader reader = null;
if (!isResultExpected)
{
command.ExecuteNonQuery();
}
else
{
reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
}
return this.CheckStateAndRead(reader);
}
private ScriptExecutionResult CheckStateAndRead(IDataReader reader = null)
{
ScriptExecutionResult result = ScriptExecutionResult.Success;
if (!isResultExpected)
{
lock (this)
{
if (state == BatchState.Cancelling)
{
result = ScriptExecutionResult.Cancel;
}
else
{
result = ScriptExecutionResult.Success;
state = BatchState.Executed;
}
}
}
else
{
lock (this)
{
if (state == BatchState.Cancelling)
{
result = ScriptExecutionResult.Cancel;
}
else
{
state = BatchState.ProcessingResults;
}
}
if (result != ScriptExecutionResult.Cancel)
{
ScriptExecutionResult batchExecutionResult = ScriptExecutionResult.Success;
if (reader != null)
{
bool hasNextResult = false;
do
{
// if there were no results coming from the server, then the FieldCount is 0
if (reader.FieldCount <= 0)
{
hasNextResult = reader.NextResult();
continue;
}
batchExecutionResult = ProcessResultSet(reader);
if (batchExecutionResult != ScriptExecutionResult.Success)
{
result = batchExecutionResult;
break;
}
RaiseBatchResultSetFinished();
hasNextResult = reader.NextResult();
} while (hasNextResult);
}
if (hasErrors)
{
Debug.WriteLine("DoBatchExecution: successfull processed result set, but there were errors shown to the user");
result = ScriptExecutionResult.Failure;
}
if (result != ScriptExecutionResult.Cancel)
{
lock (this)
{
state = BatchState.Executed;
}
}
}
}
if (reader != null)
{
try
{
// reader.Close() doesn't actually close the reader
// so explicitly dispose the reader
reader.Dispose();
reader = null;
}
catch (OutOfMemoryException)
{
throw;
}
catch (SqlException)
{
// nothing
}
}
return result;
}
#endregion
}
}

View File

@@ -0,0 +1,75 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
///<summary>
/// Class to get text from the BatchParser and convert them into batches
///</summary>
public class BatchDefinition
{
/// <summary>
/// Constructor method for a BatchDefinition
/// </summary>
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;
}
/// <summary>
/// Get starting line of the BatchDefinition
/// </summary>
public int StartLine
{
get; private set;
}
/// <summary>
/// Get ending line of the BatchDefinition
/// </summary>
public int EndLine
{
get; private set;
}
/// <summary>
/// Get starting column of the BatchDefinition
/// </summary>
public int StartColumn
{
get; private set;
}
/// <summary>
/// Get ending column of the BatchDefinition
/// </summary>
public int EndColumn
{
get; private set;
}
/// <summary>
/// Get batch text associated with the BatchDefinition
/// </summary>
public string BatchText
{
get; private set;
}
/// <summary>
/// Get number of times to execute this batch
/// </summary>
public int BatchExecutionCount
{
get; private set;
}
}
}

View File

@@ -0,0 +1,142 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data.SqlClient;
using Microsoft.SqlTools.ManagedBatchParser;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Error totalAffectedRows for a Batch
/// </summary>
public class BatchErrorEventArgs : EventArgs
{
#region Private Fields
private string message = string.Empty;
private string description = string.Empty;
private int line = -1;
private TextSpan textSpan;
private Exception exception;
private SqlError error;
#endregion
#region Constructors / Destructor
/// <summary>
/// Default constructor
/// </summary>
private BatchErrorEventArgs()
{
}
/// <summary>
/// Constructor with message and no description
/// </summary>
internal BatchErrorEventArgs(string message)
: this(message, null)
{
}
/// <summary>
/// Constructor with exception and no description
/// </summary>
internal BatchErrorEventArgs(string message, Exception ex)
: this(message, string.Empty, ex)
{
}
/// <summary>
/// Constructor with message and description
/// </summary>
internal BatchErrorEventArgs(string message, string description, Exception ex)
: this(message, description, -1, new TextSpan(), ex)
{
}
internal BatchErrorEventArgs(string message, SqlError error, TextSpan textSpan, Exception ex)
{
string desc = error != null ? error.Message : null;
if (error.Number == 7202)
{
desc += " " + Environment.NewLine + SR.TroubleshootingAssistanceMessage;
}
int lineNumber = error != null ? error.LineNumber : -1;
Init(message, desc, lineNumber, textSpan, ex);
this.error = error;
}
/// <summary>
/// Constructor with message, description, textspan and line number
/// </summary>
internal BatchErrorEventArgs(string message, string description, int line, TextSpan textSpan, Exception ex)
{
Init(message, description, line, textSpan, ex);
}
private void Init(string message, string description, int line, TextSpan textSpan, Exception ex)
{
this.message = message;
this.description = description;
this.line = line;
this.textSpan = textSpan;
exception = ex;
}
#endregion
#region Public properties
public string Message
{
get
{
return message;
}
}
public string Description
{
get
{
return description;
}
}
public int Line
{
get
{
return line;
}
}
public TextSpan TextSpan
{
get
{
return textSpan;
}
}
public Exception Exception
{
get
{
return exception;
}
}
public SqlError Error
{
get { return error; }
}
#endregion
}
}

View File

@@ -0,0 +1,58 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Event args for notification about non-error message
/// </summary>
public class BatchMessageEventArgs : EventArgs
{
private readonly string message = string.Empty;
private readonly string detailedMessage = string.Empty;
private readonly SqlError error;
private BatchMessageEventArgs()
{
}
internal BatchMessageEventArgs(string msg)
: this(string.Empty, msg)
{
}
internal BatchMessageEventArgs(string detailedMsg, string msg) : this(detailedMsg, msg, null)
{
}
internal BatchMessageEventArgs(string detailedMsg, string msg, SqlError error)
{
message = msg;
detailedMessage = detailedMsg;
this.error = error;
}
public string Message
{
get
{
return message;
}
}
public string DetailedMessage
{
get
{
return detailedMessage;
}
}
public SqlError Error { get { return error; } }
}
}

View File

@@ -0,0 +1,161 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Class that parses queries into batches
/// </summary>
public class BatchParser :
ICommandHandler,
IVariableResolver
{
#region Private fields
protected ScriptMessageDelegate scriptMessageDelegate;
protected ScriptErrorDelegate scriptErrorDelegate;
protected ExecuteDelegate executeDelegate;
protected HaltParserDelegate haltParserDelegate;
private int startingLine = 0;
protected bool variableSubstitutionDisabled = false;
#endregion
#region Public delegates
public delegate void HaltParserDelegate();
public delegate void ScriptMessageDelegate(string message);
public delegate void ScriptErrorDelegate(string message, ScriptMessageType messageType);
public delegate bool ExecuteDelegate(string batchScript, int num, int lineNumber);
#endregion
#region Constructors / Destructor
public BatchParser()
{
}
#endregion
#region Public properties
public ScriptMessageDelegate Message
{
get { return scriptMessageDelegate; }
set { scriptMessageDelegate = value; }
}
public ScriptErrorDelegate ErrorMessage
{
get { return scriptErrorDelegate; }
set { scriptErrorDelegate = value; }
}
public ExecuteDelegate Execute
{
get { return executeDelegate; }
set { executeDelegate = value; }
}
public HaltParserDelegate HaltParser
{
get { return haltParserDelegate; }
set { haltParserDelegate = value; }
}
public int StartingLine
{
get { return startingLine; }
set { startingLine = value; }
}
#endregion
#region ICommandHandler Members
/// <summary>
/// Take approptiate action on the parsed batches
/// </summary>
public BatchParserAction Go(TextBlock batch, int repeatCount)
{
string str;
LineInfo lineInfo;
batch.GetText(!variableSubstitutionDisabled, out str, out lineInfo);
bool executeResult = false;
if (executeDelegate != null)
{
executeResult = executeDelegate(str, repeatCount, lineInfo.GetStreamPositionForOffset(0).Line + startingLine - 1);
}
return executeResult ? BatchParserAction.Continue : BatchParserAction.Abort;
}
#endregion
#region Protected methods
/// <summary>
/// Called when the script parsing has errors/warnings
/// </summary>
/// <param name="message"></param>
/// <param name="messageType"></param>
protected void RaiseScriptError(string message, ScriptMessageType messageType)
{
if (scriptErrorDelegate != null)
{
scriptErrorDelegate(message, messageType);
}
}
/// <summary>
/// Called on parsing info message
/// </summary>
/// <param name="message"></param>
/// <param name="messageType"></param>
protected void RaiseScriptMessage(string message)
{
if (scriptMessageDelegate != null)
{
scriptMessageDelegate(message);
}
}
/// <summary>
/// Called on parsing info message
/// </summary>
/// <param name="message"></param>
/// <param name="messageType"></param>
protected void RaiseHaltParser()
{
if (haltParserDelegate != null)
{
haltParserDelegate();
}
}
#endregion
public virtual BatchParserAction OnError(Token token, OnErrorAction action)
{
throw new NotImplementedException("The method or operation is not implemented.");
}
public virtual BatchParserAction Include(TextBlock filename, out System.IO.TextReader stream, out string newFilename)
{
throw new NotImplementedException("The method or operation is not implemented.");
}
public virtual string GetVariable(PositionStruct pos, string name)
{
throw new NotImplementedException("The method or operation is not implemented.");
}
public virtual void SetVariable(PositionStruct pos, string name, string value)
{
throw new NotImplementedException("The method or operation is not implemented.");
}
public void DisableVariableSubstitution()
{
variableSubstitutionDisabled = true;
}
}
}

View File

@@ -0,0 +1,33 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Class associated with batch parser execution errors
/// </summary>
public class BatchParserExecutionErrorEventArgs : BatchErrorEventArgs
{
private readonly ScriptMessageType messageType;
/// <summary>
/// Constructor method for BatchParserExecutionErrorEventArgs class
/// </summary>
public BatchParserExecutionErrorEventArgs(string errorLine, string message, ScriptMessageType messageType)
: base(errorLine, message, null)
{
this.messageType = messageType;
}
public ScriptMessageType MessageType
{
get
{
return messageType;
}
}
}
}

View File

@@ -0,0 +1,48 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Class associated with batch parser execution finished event
/// </summary>
public class BatchParserExecutionFinishedEventArgs : EventArgs
{
private readonly Batch batch = null;
private readonly ScriptExecutionResult result;
private BatchParserExecutionFinishedEventArgs()
{
}
/// <summary>
/// Constructor method for the class
/// </summary>
public BatchParserExecutionFinishedEventArgs(ScriptExecutionResult batchResult, Batch batch)
{
this.batch = batch;
result = batchResult;
}
public Batch Batch
{
get
{
return batch;
}
}
public ScriptExecutionResult ExecutionResult
{
get
{
return result;
}
}
}
}

View File

@@ -0,0 +1,50 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Class associated with batch parser execution start event
/// </summary>
public class BatchParserExecutionStartEventArgs : EventArgs
{
private readonly Batch batch = null;
private readonly TextSpan textSpan;
private BatchParserExecutionStartEventArgs()
{
}
/// <summary>
/// Contructor method for the class
/// </summary>
public BatchParserExecutionStartEventArgs(TextSpan textSpan, Batch batch)
{
this.batch = batch;
this.textSpan = textSpan;
}
public Batch Batch
{
get
{
return batch;
}
}
public TextSpan TextSpan
{
get
{
return textSpan;
}
}
}
}

View File

@@ -0,0 +1,134 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using Microsoft.SqlTools.ManagedBatchParser;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Class for handling SQL CMD by Batch Parser
/// </summary>
public class BatchParserSqlCmd : BatchParser
{
/// <summary>
/// The internal variables that can be used in SqlCommand substitution.
/// These variables take precedence over environment variables.
/// </summary>
private Dictionary<string, string> internalVariables = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
private ConnectionChangedDelegate connectionChangedDelegate;
private ErrorActionChangedDelegate errorActionChangedDelegate;
public delegate void ConnectionChangedDelegate(SqlConnectionStringBuilder connectionstringBuilder);
public delegate void ErrorActionChangedDelegate(OnErrorAction ea);
/// <summary>
/// Constructor taking a Parser instance
/// </summary>
/// <param name="parser"></param>
public BatchParserSqlCmd()
: base()
{
// nothing
}
public ConnectionChangedDelegate ConnectionChanged
{
get { return connectionChangedDelegate; }
set { connectionChangedDelegate = value; }
}
public ErrorActionChangedDelegate ErrorActionChanged
{
get { return errorActionChangedDelegate; }
set { errorActionChangedDelegate = value; }
}
/// <summary>
/// Looks for any environment variable or internal variable.
/// </summary>
public override string GetVariable(PositionStruct pos, string name)
{
if (variableSubstitutionDisabled)
{
return null;
}
string value;
// Internally defined variables have higher precedence over environment variables.
if (!internalVariables.TryGetValue(name, out value))
{
value = Environment.GetEnvironmentVariables()[name] as string;
}
if (value == null)
{
RaiseScriptError(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionError_VariableNotFound, name), ScriptMessageType.FatalError);
RaiseHaltParser();
// TODO: Halt the parser, should get/set variable have ParserAction.Abort/Continue (like original?)
}
return value;
}
/// <summary>
/// Set environment or internal variable
/// </summary>
public override void SetVariable(PositionStruct pos, string name, string value)
{
if (variableSubstitutionDisabled)
{
return;
}
if (value == null)
{
if (internalVariables.ContainsKey(name))
{
internalVariables.Remove(name);
}
}
else
{
internalVariables[name] = value;
}
}
public Dictionary<string, string> InternalVariables
{
get { return internalVariables; }
set { internalVariables = value; }
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ppIBatchSource")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "fileName")]
public override BatchParserAction Include(TextBlock filename, out TextReader stream, out string newFilename)
{
stream = null;
newFilename = null;
RaiseScriptError(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionError_CommandNotSupported, "Include"), ScriptMessageType.Error);
return BatchParserAction.Abort;
}
/// <summary>
/// Method to deal with errors
/// </summary>
public override BatchParserAction OnError(Token token, OnErrorAction ea)
{
if (errorActionChangedDelegate != null)
{
errorActionChangedDelegate(ea);
}
return BatchParserAction.Continue;
}
}
}

View File

@@ -0,0 +1,52 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
/// <summary>
/// Class associated with setting batch results
/// </summary>
public class BatchResultSetEventArgs : EventArgs
{
private readonly IDataReader dataReader = null;
private readonly ShowPlanType expectedShowPlan = ShowPlanType.None;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="dataReader"></param>
internal BatchResultSetEventArgs(IDataReader dataReader, ShowPlanType expectedShowPlan)
{
this.dataReader = dataReader;
this.expectedShowPlan = expectedShowPlan;
}
/// <summary>
/// Data reader associated with the result set
/// </summary>
public IDataReader DataReader
{
get
{
return dataReader;
}
}
/// <summary>
/// Show Plan to be expected if any during the execution
/// </summary>
public ShowPlanType ExpectedShowPlan
{
get
{
return expectedShowPlan;
}
}
}
}

View File

@@ -0,0 +1,277 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Specialized;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
public class ExecutionEngineConditions
{
#region Private fields
private static class Consts
{
public static string On = "ON";
public static string Off = "OFF";
public static string ParseOnly = "SET PARSEONLY {0}";
public static string NoExec = "SET NOEXEC {0}";
public static string StatisticsIO = "SET STATISTICS IO {0}";
public static string StatisticsTime = "SET STATISTICS TIME {0}";
public static string ShowPlanXml = "SET SHOWPLAN_XML {0}";
public static string ShowPlanAll = "SET SHOWPLAN_ALL {0}";
public static string ShowPlanText = "SET SHOWPLAN_TEXT {0}";
public static string StatisticsXml = "SET STATISTICS XML {0}";
public static string StatisticsProfile = "SET STATISTICS PROFILE {0}";
public static string BeginTrans = "BEGIN TRAN";
public static string CommitTrans = "COMMIT TRAN";
public static string Rollback = "ROLLBACK";
public static string BatchSeparator = "GO";
public static string Reset = "SET NOEXEC, FMTONLY OFF, PARSEONLY, SET SHOWPLAN_ALL, SET SHOWPLAN_TEXT";
}
private static readonly int stateParseOnly = BitVector32.CreateMask();
private static readonly int stateTransactionWrapped = BitVector32.CreateMask(stateParseOnly);
private static readonly int stateHaltOnError = BitVector32.CreateMask(stateTransactionWrapped);
private static readonly int stateEstimatedShowPlan = BitVector32.CreateMask(stateHaltOnError);
private static readonly int stateActualShowPlan = BitVector32.CreateMask(stateEstimatedShowPlan);
private static readonly int stateSuppressProviderMessageHeaders = BitVector32.CreateMask(stateActualShowPlan);
private static readonly int stateNoExec = BitVector32.CreateMask(stateSuppressProviderMessageHeaders);
private static readonly int stateStatisticsIO = BitVector32.CreateMask(stateNoExec);
private static readonly int stateShowPlanText = BitVector32.CreateMask(stateStatisticsIO);
private static readonly int stateStatisticsTime = BitVector32.CreateMask(stateShowPlanText);
private static readonly int stateSqlCmd = BitVector32.CreateMask(stateStatisticsTime);
private static readonly int stateScriptExecutionTracked = BitVector32.CreateMask(stateSqlCmd);
private BitVector32 state = new BitVector32();
private string batchSeparator = Consts.BatchSeparator;
#endregion
#region Constructors / Destructor
/// <summary>
/// Default constructor
/// </summary>
public ExecutionEngineConditions()
{
// nothing
}
/// <summary>
/// Overloaded constructor taking another ExecutionEngineCondition object as a reference
/// </summary>
public ExecutionEngineConditions(ExecutionEngineConditions condition)
{
state = condition.state;
batchSeparator = condition.batchSeparator;
}
#endregion
#region Statement strings
public static string ShowPlanXmlStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ShowPlanXml, (isOn ? Consts.On : Consts.Off));
}
public static string ShowPlanAllStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ShowPlanAll, (isOn ? Consts.On : Consts.Off));
}
public static string ShowPlanTextStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ShowPlanText, (isOn ? Consts.On : Consts.Off));
}
public static string StatisticsXmlStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsXml, (isOn ? Consts.On : Consts.Off));
}
public static string StatisticsProfileStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsProfile, (isOn ? Consts.On : Consts.Off));
}
public static string ParseOnlyStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ParseOnly, (isOn ? Consts.On : Consts.Off));
}
public static string NoExecStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.NoExec, (isOn ? Consts.On : Consts.Off));
}
public static string StatisticsIOStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsIO, (isOn ? Consts.On : Consts.Off));
}
public static string StatisticsTimeStatement(bool isOn)
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsTime, (isOn ? Consts.On : Consts.Off));
}
public static string BeginTransactionStatement
{
get { return Consts.BeginTrans; }
}
public static string CommitTransactionStatement
{
get { return Consts.CommitTrans; }
}
public static string RollbackTransactionStatement
{
get { return Consts.Rollback; }
}
public static string BatchSeparatorStatement
{
get { return Consts.BatchSeparator; }
}
public static string ResetStatement
{
get { return Consts.Reset; }
}
#endregion
#region Public properties
/// <summary>
/// Checks the syntax of each Transact-SQL statement and returns any error messages
/// without compiling or executing the statement.
/// </summary>
public bool IsParseOnly
{
get { return state[stateParseOnly]; }
set { state[stateParseOnly] = value; }
}
/// <summary>
/// Script is wrapped withint BEGIN TRAN/COMMIT-ROLLBACK
/// </summary>
public bool IsTransactionWrapped
{
get { return state[stateTransactionWrapped]; }
set { state[stateTransactionWrapped] = value; }
}
/// <summary>
/// Gets or a set a value indicating whether script execution is tracked
/// </summary>
public bool IsScriptExecutionTracked
{
get { return state[stateScriptExecutionTracked]; }
set { state[stateScriptExecutionTracked] = value; }
}
/// <summary>
/// Halts the execution if an error is found
/// </summary>
public bool IsHaltOnError
{
get { return state[stateHaltOnError]; }
set { state[stateHaltOnError] = value; }
}
/// <summary>
/// Use estimated show plan
/// </summary>
public bool IsEstimatedShowPlan
{
get { return state[stateEstimatedShowPlan]; }
set { state[stateEstimatedShowPlan] = value; }
}
/// <summary>
/// Use actual show plan
/// </summary>
public bool IsActualShowPlan
{
get { return state[stateActualShowPlan]; }
set { state[stateActualShowPlan] = value; }
}
/// <summary>
/// Use Source information on messages shown to the user
/// </summary>
public bool IsSuppressProviderMessageHeaders
{
get { return state[stateSuppressProviderMessageHeaders]; }
set { state[stateSuppressProviderMessageHeaders] = value; }
}
/// <summary>
/// SET NO EXEC {on/off}
/// </summary>
public bool IsNoExec
{
get { return state[stateNoExec]; }
set { state[stateNoExec] = value; }
}
/// <summary>
/// SET STATISTICS IO {on/off}
/// </summary>
public bool IsStatisticsIO
{
get { return state[stateStatisticsIO]; }
set { state[stateStatisticsIO] = value; }
}
/// <summary>
/// SET SHOWPLAN_TEXT {on/off}
/// </summary>
public bool IsShowPlanText
{
get { return state[stateShowPlanText]; }
set { state[stateShowPlanText] = value; }
}
/// <summary>
/// SET STATISTICS IO {on/off}
/// </summary>
public bool IsStatisticsTime
{
get { return state[stateStatisticsTime]; }
set { state[stateStatisticsTime] = value; }
}
/// <summary>
/// SqlCmd support
/// </summary>
public bool IsSqlCmd
{
get { return state[stateSqlCmd]; }
set { state[stateSqlCmd] = value; }
}
/// <summary>
/// Batch separator statement
/// </summary>
public string BatchSeparator
{
get
{
return batchSeparator;
}
set
{
Validate.IsNotNullOrEmptyString(nameof(value), value);
batchSeparator = value;
}
}
#endregion
}
}

View File

@@ -0,0 +1,16 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
internal enum ExecutionState
{
Initial,
Executing,
ExecutingBatch,
Cancelling,
Discarded
}
}

View File

@@ -0,0 +1,40 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
public interface IBatchEventsHandler
{
/// <summary>
/// fired when there is an error message from the server
/// </summary>
void OnBatchError(object sender, BatchErrorEventArgs args);
/// <summary>
/// fired when there is a message from the server
/// </summary>
void OnBatchMessage(object sender, BatchMessageEventArgs args);
/// <summary>
/// fired when there is a new result set available. It is guarnteed
/// to be fired from the same thread that called Execute method
/// </summary>
void OnBatchResultSetProcessing(object sender, BatchResultSetEventArgs args);
/// <summary>
/// fired when we've done absolutely all actions for the current result set
/// </summary>
void OnBatchResultSetFinished(object sender, EventArgs args);
/// <summary>
/// fired when the batch recieved cancel request BEFORE it
/// initiates cancel operation. Note that it is fired from a
/// different thread then the one used to kick off execution
/// </summary>
void OnBatchCancelling(object sender, EventArgs args);
}
}

View File

@@ -0,0 +1,143 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
public class ScriptExecutionArgs : EventArgs
{
private IDbConnection connection;
private IBatchEventsHandler batchEventHandlers;
private int startingLine;
private Dictionary<string, string> cmdVariables;
#region Constructors / Destructor
/// <summary>
/// Constructor method for ScriptExecutionArgs
/// </summary>
public ScriptExecutionArgs(
string script,
SqlConnection connection,
int timeOut,
ExecutionEngineConditions conditions,
IBatchEventsHandler batchEventHandlers)
: this (script, (IDbConnection)connection, timeOut, conditions, batchEventHandlers)
{
// nothing
}
/// <summary>
/// Constructor method for ScriptExecutionArgs
/// </summary>
public ScriptExecutionArgs(
string script,
SqlConnection connection,
int timeOut,
ExecutionEngineConditions conditions,
IBatchEventsHandler batchEventHandlers,
int startingLine,
IDictionary<string,string> variables)
: this(script, (IDbConnection) connection, timeOut, conditions, batchEventHandlers, startingLine, variables)
{
// nothing
}
/// <summary>
/// Constructor method for ScriptExecutionArgs
/// </summary>
public ScriptExecutionArgs(
string script,
IDbConnection connection,
int timeOut,
ExecutionEngineConditions conditions,
IBatchEventsHandler batchEventHandlers)
: this(script, connection, timeOut, conditions, batchEventHandlers, 0, null)
{
// nothing
}
/// <summary>
/// Constructor method for ScriptExecutionArgs
/// </summary>
public ScriptExecutionArgs(
string script,
IDbConnection connection,
int timeOut,
ExecutionEngineConditions conditions,
IBatchEventsHandler batchEventHandlers,
int startingLine,
IDictionary<string, string> variables)
{
Script = script;
this.connection = connection;
TimeOut = timeOut;
Conditions = conditions;
this.batchEventHandlers = batchEventHandlers;
this.startingLine = startingLine;
if (variables != null)
{
foreach (var variable in variables)
{
Variables[variable.Key] = variable.Value;
}
}
}
#endregion
#region Public properties
public string Script { get; set; }
// FUTURE CLEANUP: Remove in favor of general signature (IDbConnection) - #920978
public SqlConnection Connection
{
get { return connection as SqlConnection; }
set { connection = value as SqlConnection; }
}
public IDbConnection ReliableConnection
{
get { return connection; }
set { connection = value; }
}
public int TimeOut { get; set; }
internal ExecutionEngineConditions Conditions { get; set; }
internal IBatchEventsHandler BatchEventHandlers
{
get { return batchEventHandlers; }
set { batchEventHandlers = value; }
}
internal int StartingLine
{
get { return startingLine; }
set { startingLine = value; }
}
internal Dictionary<string, string> Variables
{
get
{
if (cmdVariables == null)
{
cmdVariables = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
}
return cmdVariables;
}
}
#endregion
}
}

View File

@@ -0,0 +1,29 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
public class ScriptExecutionFinishedEventArgs : EventArgs
{
internal ScriptExecutionFinishedEventArgs(ScriptExecutionResult result)
{
ExecutionResult = result;
}
public ScriptExecutionResult ExecutionResult
{
private set;
get;
}
public bool Disposing
{
get;
set;
}
}
}

View File

@@ -0,0 +1,20 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
[Flags]
public enum ScriptExecutionResult
{
Success = 0x1,
Failure = 0x2,
Cancel = 0x4,
Halted = 0x8,
All = 0x0F
}
}

View File

@@ -0,0 +1,14 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
public enum ScriptMessageType
{
FatalError,
Error,
Warning
}
}

View File

@@ -0,0 +1,20 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
[System.Flags]
public enum ShowPlanType
{
None = 0x0,
ActualExecutionShowPlan = 0x1,
ActualXmlShowPlan = 0x2,
EstimatedExecutionShowPlan = 0x4,
EstimatedXmlShowPlan = 0x8,
AllXmlShowPlan = ActualXmlShowPlan | EstimatedXmlShowPlan,
AllExecutionShowPlan = ActualExecutionShowPlan | EstimatedExecutionShowPlan,
AllShowPlan = AllExecutionShowPlan | AllXmlShowPlan
}
}

View File

@@ -0,0 +1,15 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
{
public struct TextSpan
{
public int iEndIndex;
public int iEndLine;
public int iStartIndex;
public int iStartLine;
}
}

View File

@@ -0,0 +1,16 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.IO;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public interface ICommandHandler
{
BatchParserAction Go(TextBlock batch, int repeatCount);
BatchParserAction OnError(Token token, OnErrorAction action);
BatchParserAction Include(TextBlock filename, out TextReader stream, out string newFilename);
}
}

View File

@@ -0,0 +1,13 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public interface IVariableResolver
{
string GetVariable(PositionStruct pos, string name);
void SetVariable(PositionStruct pos, string name, string value);
}
}

View File

@@ -0,0 +1,748 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using Microsoft.SqlTools.ManagedBatchParser;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
/// <summary>
/// Lexer class for the SMO Batch Parser
/// </summary>
public sealed class Lexer : IDisposable
{
private LexerInput currentInput;
private bool popInputAtNextConsume;
private Token currentToken;
private ErrorCode errorCode = ErrorCode.Success;
private readonly Stack<LexerInput> inputStack = new Stack<LexerInput>();
private Func<bool> lexerFunc;
private TextRuleFlags textRuleFlags;
private PositionStruct tokenBeginPosition;
/// <summary>
/// Constructor for the lexer class used by SMO Batch Parser
/// </summary>
public Lexer(TextReader input, string name)
{
currentInput = new LexerInput(input, name);
currentToken = null;
RecognizeSqlCmdSyntax = true;
SetState(RuleLine);
}
/// <summary>
/// Get current token for the lexer
/// </summary>
public Token CurrentToken
{
get {
return currentToken;
}
}
/// <summary>
/// Get current token type for the lexer
/// </summary>
public LexerTokenType CurrentTokenType
{
get { return currentToken.TokenType; }
}
/// <summary>
/// Consume the token
/// </summary>
public void ConsumeToken()
{
if (currentInput == null)
{
return;
}
bool result;
tokenBeginPosition = new PositionStruct(currentInput.CurrentLine, currentInput.CurrentColumn, currentInput.CurrentOffset, currentInput.Filename);
do
{
if (popInputAtNextConsume)
{
PopAndCloseInput();
popInputAtNextConsume = false;
}
do
{
result = lexerFunc();
} while (result == false);
if (CurrentTokenType == LexerTokenType.Eof)
{
popInputAtNextConsume = true;
if(inputStack.Count > 0)
{
// report as empty NewLine token
currentToken = new Token(
LexerTokenType.NewLine, tokenBeginPosition, tokenBeginPosition, string.Empty, tokenBeginPosition.Filename);
}
}
} while (result == false);
}
public void Dispose()
{
while (inputStack.Count > 0)
{
PopAndCloseInput();
}
}
public void PopAndCloseInput()
{
if (currentInput != null)
{
currentInput.Dispose();
currentInput = null;
}
if (inputStack.Count > 0)
{
currentInput = inputStack.Pop();
SetState(RuleLine);
}
}
static string GetCircularReferenceErrorMessage(string filename)
{
return string.Format(CultureInfo.CurrentCulture, SR.BatchParser_CircularReference, filename);
}
/// <summary>
/// Push current input into the stack
/// </summary>
public void PushInput(TextReader reader, string name)
{
Debug.Assert(currentToken != null &&
(currentToken.TokenType == LexerTokenType.NewLine || currentToken.TokenType == LexerTokenType.Eof), "New input can only be pushed after new line token or EOF");
if (name.Equals(currentInput.Filename, StringComparison.OrdinalIgnoreCase))
{
RaiseError(ErrorCode.CircularReference, GetCircularReferenceErrorMessage(name));
}
foreach (LexerInput input in inputStack)
{
if (name.Equals(input.Filename, StringComparison.OrdinalIgnoreCase))
{
RaiseError(ErrorCode.CircularReference, GetCircularReferenceErrorMessage(name));
}
}
inputStack.Push(currentInput);
currentInput = new LexerInput(reader, name);
SetState(RuleLine);
// We don't want to close the input, in that case the current file would be taken out of the
// input stack and cycle detection won't work.
popInputAtNextConsume = false;
ConsumeToken();
}
private void AcceptBlockComment()
{
char? ch;
int nestingCount = 0;
Consume();
do
{
Consume();
ch = Lookahead();
if (ch.HasValue == false)
{
RaiseError(ErrorCode.CommentNotTerminated,
string.Format(CultureInfo.CurrentCulture, SR.BatchParser_CommentNotTerminated));
}
if (ch.Value == '*')
{
char? ch2 = Lookahead(1);
if (ch2.HasValue && ch2.Value == '/')
{
Consume();
if (nestingCount == 0)
{
Consume();
break;
}
nestingCount--;
}
}
else if (ch.Value == '/')
{
char? ch2 = Lookahead(1);
if (ch2.HasValue && ch2.Value == '*')
{
Consume();
nestingCount++;
}
}
} while (true);
SetToken(LexerTokenType.Comment);
}
private void AcceptLineComment()
{
char? ch;
Consume();
do
{
Consume();
ch = Lookahead();
if (ch.HasValue == false || IsNewLineChar(ch.Value))
{
break;
}
} while (true);
SetToken(LexerTokenType.Comment);
}
private void AcceptIdentifier()
{
char? ch;
do
{
Consume();
ch = Lookahead();
} while (ch.HasValue && IsIdentifierChar(ch.Value));
SetToken(LexerTokenType.Text);
}
private void AcceptNewLine()
{
char? ch = Lookahead();
if (ch == '\r')
{
Consume();
ch = Lookahead();
}
if (ch == '\n')
{
Consume();
}
SetToken(LexerTokenType.NewLine);
}
/// <summary>
/// This method reads ahead until the closingChar is found. When closingChar is found,
/// the next character is checked. If it's the same as closingChar, the character is
/// escaped and the method resumes looking for a non-escaped closingChar.
/// </summary>
private void AcceptEscapableQuotedText(char closingChar)
{
char? ch;
while (true)
{
Consume();
ch = Lookahead();
if (!ch.HasValue)
{
// we reached the end without finding our closing character. that's an error.
RaiseError(ErrorCode.StringNotTerminated, SR.BatchParser_StringNotTerminated);
break;
}
if (ch == closingChar)
{
// we found the closing character. we call consume to ensure the pointer is now at the subsequent character.
Consume();
// Check whether the subsequent character is also closingChar.
// If it is, that means the closingChar is escaped, so we must continue searching.
// Otherwise, we're finished.
char? nextChar = Lookahead();
if (!nextChar.HasValue || nextChar != closingChar)
{
break;
}
}
}
}
/// <summary>
/// This method reads ahead until the closingChar is found. This method does not allow for escaping
/// of the closingChar.
/// </summary>
private void AcceptQuotedText(char closingChar)
{
char? ch;
do
{
Consume();
ch = Lookahead();
} while (ch.HasValue && ch != closingChar);
if (ch.HasValue == false)
{
RaiseError(ErrorCode.StringNotTerminated, SR.BatchParser_StringNotTerminated);
}
else
{
Consume();
}
}
private void AcceptWhitespace()
{
char? ch;
do
{
Consume();
ch = Lookahead();
} while (ch.HasValue && IsWhitespaceChar(ch.Value));
SetToken(LexerTokenType.Whitespace);
}
private void Consume()
{
currentInput.Consume();
}
private void ChangeStateToBatchCommand(Token token)
{
switch (token.TokenType)
{
case LexerTokenType.Setvar:
SetState(RuleSetvar);
break;
case LexerTokenType.Go:
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment);
break;
case LexerTokenType.Include:
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeDoubleQuotedString);
break;
case LexerTokenType.OnError:
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment);
break;
default:
SetTextState(TextRuleFlags.ReportWhitespace);
break;
}
}
private static bool IsDigit(char ch)
{
return ch >= '0' && ch <= '9';
}
private static bool IsLetter(char ch)
{
return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
}
private bool IsNewLineChar(char ch)
{
return ch == '\r' || ch == '\n';
}
private bool IsWhitespaceChar(char ch)
{
return ch == ' ' || ch == '\t';
}
private char? Lookahead()
{
return currentInput.Lookahead();
}
private char? Lookahead(int lookahead)
{
return currentInput.Lookahead(lookahead);
}
private bool RuleError()
{
// lexer repeats last error
Parser.RaiseError(errorCode, CurrentToken);
Debug.Fail("Parser.RaiseError should throw an exception");
return true;
}
private bool RuleLine()
{
char? ch = Lookahead();
if (!ch.HasValue)
{
SetToken(LexerTokenType.Eof);
return true;
}
switch (ch.Value)
{
case ' ':
case '\t':
AcceptWhitespace();
return true;
case '\r':
case '\n':
AcceptNewLine();
return true;
case ':':
if (RecognizeSqlCmdSyntax && TryAcceptBatchCommandAndSetToken())
{
ChangeStateToBatchCommand(currentToken);
return true;
}
break;
case 'g':
case 'G':
if (TryAccept("go", true))
{
SetToken(LexerTokenType.Go);
ChangeStateToBatchCommand(currentToken);
return true;
}
break;
}
SetTextState(TextRuleFlags.RecognizeSingleQuotedString | TextRuleFlags.RecognizeDoubleQuotedString | TextRuleFlags.RecognizeLineComment | TextRuleFlags.RecognizeBlockComment | TextRuleFlags.RecognizeBrace);
return false;
}
private bool RuleSetvar()
{
char? ch = Lookahead();
if (ch.HasValue == false)
{
SetToken(LexerTokenType.Eof);
return true;
}
switch (ch.Value)
{
case '\r':
case '\n':
AcceptNewLine();
SetState(RuleLine);
return true;
case ' ':
case '\t':
AcceptWhitespace();
return true;
default:
if (IsStartIdentifierChar(ch.Value))
{
AcceptIdentifier();
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeDoubleQuotedString);
return true;
}
break;
}
// prepare error token
do
{
Consume();
ch = Lookahead();
} while (ch != null && IsWhitespaceChar(ch.Value) == false && IsNewLineChar(ch.Value) == false);
RaiseError(ErrorCode.UnrecognizedToken);
return true;
}
private bool RuleText()
{
char? ch = Lookahead();
if (ch.HasValue == false)
{
SetToken(LexerTokenType.Eof);
return true;
}
if (ch.HasValue)
{
if (IsNewLineChar(ch.Value))
{
AcceptNewLine();
SetState(RuleLine);
return true;
}
else if (textRuleFlags.HasFlag(TextRuleFlags.ReportWhitespace) && IsWhitespaceChar(ch.Value))
{
AcceptWhitespace();
return true;
}
else if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBlockComment) || textRuleFlags.HasFlag(TextRuleFlags.RecognizeLineComment))
{
char? ch2 = Lookahead(1);
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBlockComment) && ch == '/' && ch2 == '*')
{
AcceptBlockComment();
return true;
}
else if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeLineComment) && ch == '-' && ch2 == '-')
{
AcceptLineComment();
return true;
}
}
}
while (ch.HasValue)
{
bool consumed = false;
switch (ch.Value)
{
case ' ':
case '\t':
if (textRuleFlags.HasFlag(TextRuleFlags.ReportWhitespace))
{
SetToken(LexerTokenType.Text);
return true;
}
break;
case '\r':
case '\n':
SetToken(LexerTokenType.Text);
return true;
case '"':
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeDoubleQuotedString))
{
AcceptQuotedText('"');
consumed = true;
}
break;
case '\'':
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeSingleQuotedString))
{
AcceptEscapableQuotedText('\'');
consumed = true;
}
break;
case '[':
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBrace))
{
AcceptEscapableQuotedText(']');
consumed = true;
}
break;
case '-':
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeLineComment))
{
char? ch2 = Lookahead(1);
if (ch.HasValue && ch2 == '-')
{
SetToken(LexerTokenType.Text);
return true;
}
}
break;
case '/':
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBlockComment))
{
char? ch2 = Lookahead(1);
if (ch.HasValue && ch2 == '*')
{
SetToken(LexerTokenType.Text);
return true;
}
}
break;
default:
break;
}
if (consumed == false)
{
Consume();
}
ch = Lookahead();
}
SetToken(LexerTokenType.Text);
return true;
}
private void RaiseError(ErrorCode code, string message = null)
{
SetState(RuleError);
SetToken(LexerTokenType.Error);
errorCode = code;
Parser.RaiseError(errorCode, CurrentToken, message);
}
private void SetState(Func<bool> lexerFunc)
{
this.lexerFunc = lexerFunc;
}
internal void SetTextState(TextRuleFlags textRuleFlags)
{
this.textRuleFlags = textRuleFlags;
SetState(RuleText);
}
private void SetToken(LexerTokenType lexerTokenType)
{
string text = currentInput.FlushBufferedText();
currentToken = new Token(
lexerTokenType,
tokenBeginPosition,
new PositionStruct(currentInput.CurrentLine, currentInput.CurrentColumn, currentInput.CurrentOffset, currentInput.Filename),
text,
currentInput.Filename);
}
private bool TryAccept(string text, bool wordBoundary)
{
Debug.Assert(text.Length > 0);
int i = 0;
do
{
char? ch = Lookahead(i);
if (ch.HasValue == false || char.ToLowerInvariant(ch.Value) != text[i])
{
return false;
}
i++;
} while (i < text.Length);
if (wordBoundary)
{
char? ch = Lookahead(text.Length);
if (ch != null && IsWhitespaceChar(ch.Value) == false && IsNewLineChar(ch.Value) == false
&& ch != '$' && ch != '/' && ch != '-' && ch != '\'' && ch != '"' && ch != '(' && ch != '[' && ch != '!')
{
return false;
}
}
// consume all checked characters
for (i = 0; i < text.Length; i++)
{
Consume();
}
return true;
}
private bool TryAcceptBatchCommandAndSetToken()
{
Consume(); // colon
if (TryAccept("reset", true))
{
SetToken(LexerTokenType.Reset);
return true;
}
else if (TryAccept("ed", true))
{
SetToken(LexerTokenType.Ed);
return true;
}
else if (TryAccept("!!", true))
{
SetToken(LexerTokenType.Execute);
return true;
}
else if (TryAccept("quit", true))
{
SetToken(LexerTokenType.Quit);
return true;
}
else if (TryAccept("exit", true))
{
SetToken(LexerTokenType.Exit);
return true;
}
else if (TryAccept("r", true))
{
SetToken(LexerTokenType.Include);
return true;
}
else if (TryAccept("serverlist", true))
{
SetToken(LexerTokenType.Serverlist);
return true;
}
else if (TryAccept("setvar", true))
{
SetToken(LexerTokenType.Setvar);
return true;
}
else if (TryAccept("list", true))
{
SetToken(LexerTokenType.List);
return true;
}
else if (TryAccept("error", true))
{
SetToken(LexerTokenType.ErrorCommand);
return true;
}
else if (TryAccept("out", true))
{
SetToken(LexerTokenType.Out);
return true;
}
else if (TryAccept("perftrace", true))
{
SetToken(LexerTokenType.Perftrace);
return true;
}
else if (TryAccept("connect", true))
{
SetToken(LexerTokenType.Connect);
return true;
}
else if (TryAccept("on error", true))
{
SetToken(LexerTokenType.OnError);
return true;
}
else if (TryAccept("help", true))
{
SetToken(LexerTokenType.Help);
return true;
}
else if (TryAccept("xml", true))
{
SetToken(LexerTokenType.Xml);
return true;
}
else if (TryAccept("listvar", true))
{
SetToken(LexerTokenType.ListVar);
return true;
}
return false;
}
internal static bool IsIdentifierChar(char ch)
{
return IsLetter(ch) || IsDigit(ch) || ch == '_' || ch == '-';
}
internal static bool IsStartIdentifierChar(char ch)
{
return IsLetter(ch) || ch == '_';
}
public bool RecognizeSqlCmdSyntax { get; set; }
}
}

View File

@@ -0,0 +1,186 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.IO;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
/// <summary>
/// Lexer input class used by lexer in SMO Batch Parser
/// </summary>
internal sealed class LexerInput : IDisposable
{
private readonly string filename;
private TextReader input;
private int currentLine;
private int currentColumn;
private int bufferStartOffset;
private int currentSbOffset;
private StringBuilder buffer;
/// <summary>
/// Constructor method for the LexerInput class
/// </summary>
public LexerInput(TextReader reader, string filename)
{
input = reader;
this.filename = filename;
currentLine = 1;
currentColumn = 1;
bufferStartOffset = 0;
currentSbOffset = 0;
buffer = new StringBuilder();
EnsureBytes(1);
}
/// <summary>
/// Get filename associated with lexer input
/// </summary>
public string Filename
{
get { return filename; }
}
/// <summary>
/// Get current line associated with lexer input
/// </summary>
public int CurrentLine
{
get { return currentLine; }
}
/// <summary>
/// Get current column associated with lexer input
/// </summary>
public int CurrentColumn
{
get { return currentColumn; }
}
/// <summary>
/// Consume token used by lexer input
/// </summary>
public void Consume()
{
bool newLineWithCR = false;
char? ch = Lookahead();
if (ch == null)
{
// end of stream
return;
}
else if (ch == '\r')
{
newLineWithCR = true;
}
else if (ch == '\n')
{
currentLine++;
currentColumn = 0;
}
int count = EnsureBytes(1);
if (count == 0)
{
// end of stream
return;
}
currentSbOffset++;
if (newLineWithCR && Lookahead() != '\n')
{
currentLine++;
currentColumn = 0;
}
currentColumn++;
}
public void Dispose()
{
if (input != null)
{
input.Dispose();
input = null;
}
}
/// <summary>
/// Get current offset for the lexer input
/// </summary>
public int CurrentOffset
{
get { return bufferStartOffset + currentSbOffset; }
}
/// <summary>
/// Ensure correct number of bytes to buffer
/// </summary>
public int EnsureBytes(int bytesToBuffer)
{
if (currentSbOffset + bytesToBuffer > buffer.Length)
{
if (input == null)
{
return buffer.Length - currentSbOffset;
}
int chArrayLength = bytesToBuffer - (buffer.Length - currentSbOffset) + 128;
char[] chArray = new char[chArrayLength];
int count = input.ReadBlock(chArray, 0, chArrayLength);
buffer.Append(chArray, 0, count);
if (count < chArrayLength)
{
input.Dispose();
input = null;
}
return buffer.Length - currentSbOffset;
}
return bytesToBuffer;
}
/// <summary>
/// look ahead bytes in lexer input
/// </summary>
public char? Lookahead()
{
int count = EnsureBytes(1);
if (count == 0)
{
return null;
}
return buffer[currentSbOffset];
}
/// <summary>
/// look ahead bytes in lexer input
/// </summary>
public char? Lookahead(int lookahead)
{
int count = EnsureBytes(lookahead + 1);
if (count < lookahead + 1)
{
return null;
}
return buffer[currentSbOffset + lookahead];
}
/// <summary>
/// Flush buffered text in lexer input
/// </summary>
public string FlushBufferedText()
{
string text;
text = buffer.ToString(0, currentSbOffset);
bufferStartOffset += currentSbOffset;
buffer.Remove(0, currentSbOffset);
currentSbOffset = 0;
return text;
}
}
}

View File

@@ -0,0 +1,40 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public enum LexerTokenType
{
None,
Text,
TextVerbatim,
Whitespace,
NewLine,
String,
Eof,
Error,
Comment,
// batch commands
Go,
Reset,
Ed,
Execute,
Quit,
Exit,
Include,
Serverlist,
Setvar,
List,
ErrorCommand,
Out,
Perftrace,
Connect,
OnError,
Help,
Xml,
ListVar,
}
}

View File

@@ -0,0 +1,118 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
/// <summary>
/// This class gives information about lines being parsed by
/// the Batch Parser
/// </summary>
public class LineInfo
{
private IEnumerable<Token> tokens;
private IEnumerable<VariableReference> variableRefs;
/// <summary>
/// Constructor method for the LineInfo class
/// </summary>
public LineInfo(IEnumerable<Token> tokens, IEnumerable<VariableReference> variableRefs)
{
this.tokens = tokens;
this.variableRefs = variableRefs;
}
/// <summary>
/// Gets the stream position for offset and returns a PositionStruct
/// </summary>
public PositionStruct GetStreamPositionForOffset(int offset)
{
if (variableRefs != null)
{
offset = CalculateVarsUnresolvedOffset(offset);
}
int charCount = 0;
Token lastToken = null;
foreach (Token token in tokens)
{
lastToken = token;
if (charCount + token.Text.Length > offset)
{
int line, column;
CalculateLineColumnForOffset(token, offset - charCount, out line, out column);
return new PositionStruct(line, column, token.Begin.Offset + (offset - charCount), token.Filename);
}
charCount += token.Text.Length;
}
if (lastToken != null)
{
return new PositionStruct(lastToken.End.Line, lastToken.End.Column, lastToken.End.Offset, lastToken.Filename);
}
else
{
return new PositionStruct(1, 1, 0, string.Empty);
}
}
internal static void CalculateLineColumnForOffset(Token token, int offset, out int line, out int column)
{
CalculateLineColumnForOffset(token.Text, offset, 0, token.Begin.Line, token.Begin.Column, out line, out column);
}
internal static void CalculateLineColumnForOffset(string text, int offset,
int offsetDelta, int lineDelta, int columnDelta, out int line, out int column)
{
line = lineDelta;
column = columnDelta;
int counter = offsetDelta;
while (counter < offset)
{
bool newLineWithCR = false;
if (text[counter] == '\r')
{
newLineWithCR = true;
}
else if (text[counter] == '\n')
{
line++;
column = 0;
}
counter++;
if (newLineWithCR && counter < text.Length && text[counter] != '\n')
{
line++;
column = 0;
}
column++;
}
}
private int CalculateVarsUnresolvedOffset(int offset)
{
// find offset of the beginning of variable substitution (if offset points to the middle of it)
int diff = 0;
foreach (VariableReference reference in variableRefs)
{
if (reference.Start >= offset)
{
break;
}
else if (reference.VariableValue != null && offset < reference.Start + reference.VariableValue.Length)
{
offset = reference.Start;
break;
}
if (reference.VariableValue != null)
{
diff += reference.Length - reference.VariableValue.Length;
}
}
return offset + diff;
}
}
}

View File

@@ -0,0 +1,13 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public enum OnErrorAction
{
Ignore = 0,
Exit = 1,
}
}

View File

@@ -0,0 +1,723 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security;
using System.Text;
using Microsoft.SqlTools.ManagedBatchParser;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
/// <summary>
/// The Parser class on which the Batch Parser is based on
/// </summary>
public sealed class Parser : IDisposable
{
private readonly ICommandHandler commandHandler;
private Lexer lexer;
private List<Token> tokenBuffer;
private readonly IVariableResolver variableResolver;
/// <summary>
/// Constructor for the Parser class
/// </summary>
public Parser(ICommandHandler commandHandler, IVariableResolver variableResolver, TextReader reader, string name)
{
this.commandHandler = commandHandler;
this.variableResolver = variableResolver;
lexer = new Lexer(reader, name);
tokenBuffer = new List<Token>();
}
public bool ThrowOnUnresolvedVariable { get; set; }
private Token LookaheadToken
{
get { return lexer.CurrentToken; }
}
private LexerTokenType LookaheadTokenType
{
get { return lexer.CurrentTokenType; }
}
public void Dispose()
{
if (lexer != null)
{
lexer.Dispose();
lexer = null;
}
}
private bool AcceptWhitespaceOrComment()
{
bool found = false;
while (LookaheadTokenType == LexerTokenType.Comment ||
LookaheadTokenType == LexerTokenType.Whitespace)
{
Accept();
found = true;
}
return found;
}
private bool Accept(LexerTokenType lexerTokenType)
{
if (LookaheadTokenType == lexerTokenType)
{
Accept();
return true;
}
return false;
}
private void AddTokenToStringBuffer()
{
Token token = LookaheadToken;
if (token.TokenType != LexerTokenType.Comment)
{
// All variable references must be valid
CheckVariableReferences(token);
}
if (token.TokenType == LexerTokenType.NewLine && token.Text.Length == 0)
{
// Add "virtual" token representing new line that was not in original file
PositionStruct beginPos = token.Begin;
PositionStruct endPos = new PositionStruct(beginPos.Line + 1, 1, beginPos.Offset, beginPos.Filename);
tokenBuffer.Add(new Token(LexerTokenType.NewLine, beginPos, endPos, Environment.NewLine, beginPos.Filename));
}
else
{
tokenBuffer.Add(token);
}
}
private void CheckVariableReferences(Token token)
{
// AddVariableReferences will raise error if reference is not constructed correctly
AddVariableReferences(token, 0, null);
}
private void AddVariableReferences(Token token, int offset, IList<VariableReference> variableRefs)
{
if (lexer.RecognizeSqlCmdSyntax == false)
{
// variables are recognized only in sqlcmd mode.
return;
}
string text = token.Text;
int i = 0;
int startIndex = -1;
int state = 0;
while (i < text.Length)
{
char ch = text[i];
if (state == 0 && ch == '$')
{
state = 1;
}
else if (state == 1)
{
if (ch == '(')
{
state = 2;
}
else if (ch != '$')
{
state = 0;
}
}
else if (state == 2)
{
if (Lexer.IsStartIdentifierChar(ch))
{
startIndex = i;
state = 3;
}
else
{
RaiseError(ErrorCode.InvalidVariableName, GetSubToken(token, i - 2, i + 1));
}
}
else if (state == 3)
{
if (ch == ')')
{
if (variableRefs != null)
{
variableRefs.Add(new VariableReference(offset + startIndex - 2, i - startIndex + 3, text.Substring(startIndex, i - startIndex)));
}
state = 0;
}
else if (Lexer.IsIdentifierChar(ch) == false)
{
RaiseError(ErrorCode.InvalidVariableName, GetSubToken(token, startIndex - 2, i + 1));
}
}
i++;
}
if (state == 2 || state == 3)
{
RaiseError(ErrorCode.InvalidVariableName, GetSubToken(token, startIndex - 2, i));
}
}
private static Token GetSubToken(Token token, int startOffset, int endOffset, LexerTokenType? newTokenType = null)
{
LexerTokenType tokenType = newTokenType.HasValue ? newTokenType.Value : token.TokenType;
string text = token.Text.Substring(startOffset, endOffset - startOffset);
string filename = token.Begin.Filename;
PositionStruct beginPos, endPos;
int beginLine, beginColumn;
LineInfo.CalculateLineColumnForOffset(token, startOffset, out beginLine, out beginColumn);
beginPos = new PositionStruct(beginLine, beginColumn, token.Begin.Offset + startOffset, filename);
int endLine, endColumn;
LineInfo.CalculateLineColumnForOffset(token, endOffset, out endLine, out endColumn);
endPos = new PositionStruct(endLine, endColumn, token.Begin.Offset + endOffset, filename);
return new Token(tokenType, beginPos, endPos, text, filename);
}
private LexerTokenType Accept()
{
lexer.ConsumeToken();
return LookaheadTokenType;
}
private void ExecuteBatch(int repeatCount)
{
BatchParserAction action;
action = commandHandler.Go(new TextBlock(this, tokenBuffer), repeatCount);
if (action == BatchParserAction.Abort)
{
RaiseError(ErrorCode.Aborted);
}
tokenBuffer = new List<Token>();
}
private bool Expect(LexerTokenType lexerTokenType)
{
if (LookaheadTokenType == lexerTokenType)
return true;
RaiseError(ErrorCode.TokenExpected);
return false;
}
private bool ExpectAndAccept(LexerTokenType lexerTokenType)
{
if (Accept(lexerTokenType))
return true;
RaiseError(ErrorCode.TokenExpected);
return false;
}
public void Parse()
{
Accept();
ParseLines();
}
private void ParseGo()
{
int repeatCount = 1;
AcceptWhitespaceOrComment();
if (LookaheadTokenType == LexerTokenType.Text)
{
string text = ResolveVariables(LookaheadToken, 0, null);
for (int i = 0; i < text.Length; i++)
{
if (Char.IsDigit(text[i]) == false)
{
RaiseError(ErrorCode.InvalidNumber);
}
}
bool result = Int32.TryParse(text, out repeatCount);
if (result == false)
{
RaiseError(ErrorCode.InvalidNumber);
}
Accept(LexerTokenType.Text);
AcceptWhitespaceOrComment();
}
if (LookaheadTokenType != LexerTokenType.Eof)
{
ExpectAndAccept(LexerTokenType.NewLine);
}
ExecuteBatch(repeatCount);
}
private void ParseInclude()
{
BatchParserAction action;
Accept(LexerTokenType.Whitespace);
Expect(LexerTokenType.Text);
Token token = LookaheadToken;
if (token.Text.StartsWith("--", StringComparison.Ordinal))
{
RaiseError(ErrorCode.TokenExpected);
}
lexer.SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment | TextRuleFlags.RecognizeDoubleQuotedString);
Accept();
AcceptWhitespaceOrComment();
if (LookaheadTokenType != LexerTokenType.Eof)
{
Expect(LexerTokenType.NewLine);
}
TextReader textReader;
string newFilename;
string tokenText = token.Text;
IEnumerable<Token> tokens;
if (tokenText.IndexOf('"') != -1)
{
tokens = SplitQuotedTextToken(token);
}
else
{
tokens = new[] { token };
}
action = commandHandler.Include(new TextBlock(this, tokens), out textReader, out newFilename);
if (action == BatchParserAction.Abort)
{
RaiseError(ErrorCode.Aborted);
}
if (textReader != null)
{
this.PushInput(textReader, newFilename);
}
}
private IEnumerable<Token> SplitQuotedTextToken(Token token)
{
string tokenText = token.Text;
if (tokenText.Length <= 1)
{
return new[] { token };
}
IList<Token> tokens = new List<Token>();
int offset = 0;
int quotePos = tokenText.IndexOf('"');
while (quotePos != -1)
{
int closingQuotePos;
if (quotePos != offset)
{
tokens.Add(GetSubToken(token, offset, quotePos));
offset = quotePos;
}
closingQuotePos = tokenText.IndexOf('"', quotePos + 1);
if (closingQuotePos == -1)
{
// odd number of " characters
break;
}
if (closingQuotePos + 1 < tokenText.Length && tokenText[closingQuotePos + 1] == '"')
{
// two consequtive " characters: report one of them
tokens.Add(GetSubToken(token, quotePos + 1, closingQuotePos + 1, LexerTokenType.TextVerbatim));
}
else
{
// Add the contents of the quoted string, but remove the " characters
tokens.Add(GetSubToken(token, quotePos + 1, closingQuotePos, LexerTokenType.TextVerbatim));
}
offset = closingQuotePos + 1;
quotePos = tokenText.IndexOf('"', offset);
}
if (offset != tokenText.Length)
{
tokens.Add(GetSubToken(token, offset, tokenText.Length));
}
return tokens;
}
internal void PushInput(TextReader reader, string filename)
{
lexer.PushInput(reader, filename);
}
private void ParseLines()
{
do
{
LexerTokenType tokenType = LookaheadTokenType;
switch (tokenType)
{
case LexerTokenType.OnError:
RemoveLastWhitespaceToken();
Token onErrorToken = LookaheadToken;
Accept();
ParseOnErrorCommand(onErrorToken);
break;
case LexerTokenType.Eof:
if (tokenBuffer.Count > 0)
{
ExecuteBatch(1);
}
return;
case LexerTokenType.Go:
RemoveLastWhitespaceToken();
Accept();
ParseGo();
break;
case LexerTokenType.Include:
RemoveLastWhitespaceToken();
Accept();
ParseInclude();
break;
case LexerTokenType.Comment:
case LexerTokenType.NewLine:
case LexerTokenType.Text:
case LexerTokenType.Whitespace:
AddTokenToStringBuffer();
Accept();
break;
case LexerTokenType.Setvar:
Token setvarToken = LookaheadToken;
RemoveLastWhitespaceToken();
Accept();
ParseSetvar(setvarToken);
break;
case LexerTokenType.Connect:
case LexerTokenType.Ed:
case LexerTokenType.ErrorCommand:
case LexerTokenType.Execute:
case LexerTokenType.Exit:
case LexerTokenType.Help:
case LexerTokenType.List:
case LexerTokenType.ListVar:
case LexerTokenType.Out:
case LexerTokenType.Perftrace:
case LexerTokenType.Quit:
case LexerTokenType.Reset:
case LexerTokenType.Serverlist:
case LexerTokenType.Xml:
RaiseError(ErrorCode.UnsupportedCommand,
string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionError_CommandNotSupported, tokenType));
break;
default:
RaiseError(ErrorCode.UnrecognizedToken);
break;
}
} while (true);
}
private void RemoveLastWhitespaceToken()
{
if (tokenBuffer.Count > 0 && tokenBuffer[tokenBuffer.Count - 1].TokenType == LexerTokenType.Whitespace)
{
tokenBuffer.RemoveAt(tokenBuffer.Count - 1);
}
}
private void ParseOnErrorCommand(Token onErrorToken)
{
ExpectAndAccept(LexerTokenType.Whitespace);
Expect(LexerTokenType.Text);
string action;
action = ResolveVariables(LookaheadToken, 0, null);
OnErrorAction onErrorAction;
if (action.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
onErrorAction = OnErrorAction.Exit;
}
else if (action.Equals("ignore", StringComparison.OrdinalIgnoreCase))
{
onErrorAction = OnErrorAction.Ignore;
}
else
{
RaiseError(ErrorCode.UnrecognizedToken);
return;
}
Accept(LexerTokenType.Text);
AcceptWhitespaceOrComment();
if (LookaheadTokenType != LexerTokenType.Eof)
{
Expect(LexerTokenType.NewLine);
}
BatchParserAction parserAction;
parserAction = commandHandler.OnError(onErrorToken, onErrorAction);
if (parserAction == BatchParserAction.Abort)
{
RaiseError(ErrorCode.Aborted);
}
}
private void ParseSetvar(Token setvarToken)
{
string variableName;
string variableValue = null;
Accept(LexerTokenType.Whitespace);
Expect(LexerTokenType.Text);
variableName = LookaheadToken.Text;
Accept();
Accept(LexerTokenType.Whitespace);
switch (LookaheadTokenType)
{
case LexerTokenType.Text:
// No variable substitution
variableValue = UnquoteVariableValue(LookaheadToken.Text);
lexer.SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment | TextRuleFlags.RecognizeDoubleQuotedString);
Accept();
AcceptWhitespaceOrComment();
if (LookaheadTokenType != LexerTokenType.NewLine &&
LookaheadTokenType != LexerTokenType.Eof)
{
RaiseError(ErrorCode.UnrecognizedToken);
}
if (LookaheadTokenType != LexerTokenType.Eof)
{
Accept();
}
break;
case LexerTokenType.NewLine:
case LexerTokenType.Eof:
Accept();
break;
default:
RaiseError(ErrorCode.UnrecognizedToken);
break;
}
variableResolver.SetVariable(setvarToken.Begin, variableName, variableValue);
}
internal void RaiseError(ErrorCode errorCode, string message = null)
{
RaiseError(errorCode, LookaheadToken, message);
}
internal static void RaiseError(ErrorCode errorCode, Token token, string message = null)
{
if (message == null)
{
message = string.Format(CultureInfo.CurrentCulture, SR.BatchParser_IncorrectSyntax, token.Text);
}
throw new BatchParserException(errorCode, token, message);
}
internal string ResolveVariables(Token inputToken, int offset, List<VariableReference> variableRefs)
{
List<VariableReference> variableRefsLocal = new List<VariableReference>();
AddVariableReferences(inputToken, offset, variableRefsLocal);
string inputString = inputToken.Text;
if (variableRefsLocal.Count == 0)
{
// no variable references to substitute
return inputString;
}
StringBuilder sb = new StringBuilder();
int lastChar = 0;
PositionStruct? variablePos = null;
foreach (VariableReference reference in variableRefsLocal)
{
int line;
int column;
if (variablePos != null)
{
LineInfo.CalculateLineColumnForOffset(inputToken.Text, reference.Start - offset,
variablePos.Value.Offset, variablePos.Value.Line, variablePos.Value.Column,
out line, out column);
}
else
{
LineInfo.CalculateLineColumnForOffset(inputToken, reference.Start - offset, out line, out column);
}
variablePos = new PositionStruct(
line: line,
column: column,
offset: reference.Start - offset,
filename: inputToken.Filename);
string value = variableResolver.GetVariable(variablePos.Value, reference.VariableName);
if (value == null)
{
// Undefined variable
if (ThrowOnUnresolvedVariable == true)
{
RaiseError(ErrorCode.VariableNotDefined, inputToken,
string.Format(CultureInfo.CurrentCulture, SR.BatchParser_VariableNotDefined, reference.VariableName));
}
continue;
}
reference.VariableValue = value;
sb.Append(inputToken.Text, lastChar, reference.Start - offset - lastChar);
sb.Append(value);
lastChar = reference.Start - offset + reference.Length;
}
if (lastChar != inputString.Length)
{
sb.Append(inputString, lastChar, inputString.Length - lastChar);
}
if (variableRefs != null)
{
variableRefs.AddRange(variableRefsLocal);
}
return sb.ToString();
}
private string UnquoteVariableValue(string s)
{
if (string.IsNullOrEmpty(s) == false)
{
if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == s[0])
{
// all " characters must be doubled
for (int i = 1; i < s.Length - 1; i++)
{
if (s[i] == '"')
{
if (i + 1 >= s.Length - 1 || s[i + 1] != '"')
{
RaiseError(ErrorCode.TokenExpected);
}
// skip next character
i++;
}
}
return s.Substring(1, s.Length - 2).Replace("\"\"", "\"");
}
else if (s[0] == '"' || s[s.Length - 1] == '"')
{
RaiseError(ErrorCode.TokenExpected);
}
else
{
if (s.Length >= 2 && s[0] == '-' && s[1] == '-')
{
return null;
}
if (s.IndexOf('"') >= 0)
{
RaiseError(ErrorCode.TokenExpected);
}
}
}
return s;
}
public void SetRecognizeSqlCmdSyntax(bool recognizeSqlCmdSyntax)
{
lexer.RecognizeSqlCmdSyntax = recognizeSqlCmdSyntax;
}
public static TextReader GetBufferedTextReaderForFile(string filename)
{
Exception handledException = null;
try
{
return new StringReader(File.ReadAllText(filename));
}
catch (IOException exception)
{
handledException = exception;
}
catch (NotSupportedException exception)
{
handledException = exception;
}
catch (UnauthorizedAccessException exception)
{
handledException = exception;
}
catch (SecurityException exception)
{
handledException = exception;
}
if (null != handledException)
{
throw new Exception(handledException.Message, handledException);
}
Debug.Fail("This code should not be reached.");
return null;
}
internal void SetBatchDelimiter(string batchSeparator)
{
// Not implemeneted as only GO is supported now
}
public static string TokenTypeToCommandString(LexerTokenType lexerTokenType)
{
switch (lexerTokenType)
{
case LexerTokenType.Connect:
return "Connect";
case LexerTokenType.Ed:
return "Ed";
case LexerTokenType.ErrorCommand:
return "Error";
case LexerTokenType.Execute:
return "!!";
case LexerTokenType.Exit:
return "Exit";
case LexerTokenType.Help:
return "Help";
case LexerTokenType.List:
return "List";
case LexerTokenType.ListVar:
return "ListVar";
case LexerTokenType.OnError:
return "On Error";
case LexerTokenType.Out:
return "Out";
case LexerTokenType.Perftrace:
return "PerfTrace";
case LexerTokenType.Quit:
return "Quit";
case LexerTokenType.Reset:
return "Reset";
case LexerTokenType.Serverlist:
return "ServerList";
case LexerTokenType.Xml:
return "Xml";
default:
Debug.Fail("Unknown batch parser command");
return lexerTokenType.ToString();
}
}
}
}

View File

@@ -0,0 +1,49 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
[Serializable]
public struct PositionStruct
{
private readonly int line;
private readonly int column;
private readonly int offset;
private readonly string filename;
/// <summary>
/// Constructor for the PositionStruct class
/// </summary>
public PositionStruct(int line, int column, int offset, string filename)
{
this.line = line;
this.column = column;
this.offset = offset;
this.filename = filename;
}
/// <summary>
/// Get line from the PositionStruct
/// </summary>
public int Line { get { return line; } }
/// <summary>
/// Get column from the PositionStruct
/// </summary>
public int Column { get { return column; } }
/// <summary>
/// Get offset from the PositionStruct
/// </summary>
public int Offset { get { return offset; } }
/// <summary>
/// Get file name from the PositionStruct
/// </summary>
public string Filename { get { return filename; } }
}
}

View File

@@ -0,0 +1,67 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public class TextBlock
{
private readonly Parser parser;
private readonly IEnumerable<Token> tokens;
/// <summary>
/// Constructor for the TextBlock class
/// </summary>
public TextBlock(Parser parser, Token token) : this(parser, new[] { token })
{
}
/// <summary>
/// Constructor for the TextBlock class
/// </summary>
public TextBlock(Parser parser, IEnumerable<Token> tokens)
{
this.parser = parser;
this.tokens = tokens;
}
/// <summary>
/// Get text from TextBlock
/// </summary>
public void GetText(bool resolveVariables, out string text, out LineInfo lineInfo)
{
StringBuilder sb = new StringBuilder();
List<VariableReference> variableRefs = null;
if (resolveVariables == false)
{
foreach (Token token in tokens)
{
sb.Append(token.Text);
}
}
else
{
variableRefs = new List<VariableReference>();
foreach (Token token in tokens)
{
if (token.TokenType == LexerTokenType.Text)
{
sb.Append(parser.ResolveVariables(token, sb.Length, variableRefs));
}
else
{
// comments and whitespaces do not need variable expansion
sb.Append(token.Text);
}
}
}
lineInfo = new LineInfo(tokens, variableRefs);
text = sb.ToString();
}
}
}

View File

@@ -0,0 +1,20 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
[Flags]
internal enum TextRuleFlags
{
ReportWhitespace = 1,
RecognizeDoubleQuotedString = 2,
RecognizeSingleQuotedString = 4,
RecognizeLineComment = 8,
RecognizeBlockComment = 16,
RecognizeBrace = 32,
}
}

View File

@@ -0,0 +1,47 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
public sealed class Token
{
/// <summary>
/// Token class used by the lexer in Batch Parser
/// </summary>
internal Token(LexerTokenType tokenType, PositionStruct begin, PositionStruct end, string text, string filename)
{
TokenType = tokenType;
Begin = begin;
End = end;
Text = text;
Filename = filename;
}
/// <summary>
/// Get file name associated with Token
/// </summary>
public string Filename { get; private set; }
/// <summary>
/// Get beginning position for the Token
/// </summary>
public PositionStruct Begin { get; private set; }
/// <summary>
/// Get end position for the Token
/// </summary>
public PositionStruct End { get; private set; }
/// <summary>
/// Get text assocaited with the Token
/// </summary>
public string Text { get; private set; }
/// <summary>
/// Get token type of the Token
/// </summary>
public LexerTokenType TokenType { get; private set; }
}
}

View File

@@ -0,0 +1,44 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{
/// <summary>
/// Class for reference of variables used by the lexer
/// </summary>
public sealed class VariableReference
{
/// <summary>
/// Constructor method for VariableReference class
/// </summary>
public VariableReference(int start, int length, string variableName)
{
Start = start;
Length = length;
VariableName = variableName;
VariableValue = null;
}
/// <summary>
/// Get length associated with the VariableReference
/// </summary>
public int Length { get; private set; }
/// <summary>
/// Get start position associated with the VariableReference
/// </summary>
public int Start { get; private set; }
/// <summary>
/// Get variable name associated with the VariableReference
/// </summary>
public string VariableName { get; private set; }
/// <summary>
/// Get variable value associated with the VariableReference
/// </summary>
public string VariableValue { get; internal set; }
}
}

View File

@@ -0,0 +1,381 @@
// 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.ManagedBatchParser
{
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;
}
}
public static string EE_BatchSqlMessageNoProcedureInfo
{
get
{
return Keys.GetString(Keys.EE_BatchSqlMessageNoProcedureInfo);
}
}
public static string EE_BatchSqlMessageWithProcedureInfo
{
get
{
return Keys.GetString(Keys.EE_BatchSqlMessageWithProcedureInfo);
}
}
public static string EE_BatchSqlMessageNoLineInfo
{
get
{
return Keys.GetString(Keys.EE_BatchSqlMessageNoLineInfo);
}
}
public static string EE_BatchError_Exception
{
get
{
return Keys.GetString(Keys.EE_BatchError_Exception);
}
}
public static string EE_BatchExecutionInfo_RowsAffected
{
get
{
return Keys.GetString(Keys.EE_BatchExecutionInfo_RowsAffected);
}
}
public static string EE_ExecutionNotYetCompleteError
{
get
{
return Keys.GetString(Keys.EE_ExecutionNotYetCompleteError);
}
}
public static string EE_ScriptError_Error
{
get
{
return Keys.GetString(Keys.EE_ScriptError_Error);
}
}
public static string EE_ScriptError_ParsingSyntax
{
get
{
return Keys.GetString(Keys.EE_ScriptError_ParsingSyntax);
}
}
public static string EE_ScriptError_FatalError
{
get
{
return Keys.GetString(Keys.EE_ScriptError_FatalError);
}
}
public static string EE_ExecutionInfo_FinalizingLoop
{
get
{
return Keys.GetString(Keys.EE_ExecutionInfo_FinalizingLoop);
}
}
public static string EE_ExecutionInfo_QueryCancelledbyUser
{
get
{
return Keys.GetString(Keys.EE_ExecutionInfo_QueryCancelledbyUser);
}
}
public static string EE_BatchExecutionError_Halting
{
get
{
return Keys.GetString(Keys.EE_BatchExecutionError_Halting);
}
}
public static string EE_BatchExecutionError_Ignoring
{
get
{
return Keys.GetString(Keys.EE_BatchExecutionError_Ignoring);
}
}
public static string EE_ExecutionInfo_InitializingLoop
{
get
{
return Keys.GetString(Keys.EE_ExecutionInfo_InitializingLoop);
}
}
public static string EE_ExecutionError_CommandNotSupported
{
get
{
return Keys.GetString(Keys.EE_ExecutionError_CommandNotSupported);
}
}
public static string EE_ExecutionError_VariableNotFound
{
get
{
return Keys.GetString(Keys.EE_ExecutionError_VariableNotFound);
}
}
public static string BatchParserWrapperExecutionEngineError
{
get
{
return Keys.GetString(Keys.BatchParserWrapperExecutionEngineError);
}
}
public static string BatchParserWrapperExecutionError
{
get
{
return Keys.GetString(Keys.BatchParserWrapperExecutionError);
}
}
public static string BatchParserWrapperExecutionEngineBatchMessage
{
get
{
return Keys.GetString(Keys.BatchParserWrapperExecutionEngineBatchMessage);
}
}
public static string BatchParserWrapperExecutionEngineBatchResultSetProcessing
{
get
{
return Keys.GetString(Keys.BatchParserWrapperExecutionEngineBatchResultSetProcessing);
}
}
public static string BatchParserWrapperExecutionEngineBatchResultSetFinished
{
get
{
return Keys.GetString(Keys.BatchParserWrapperExecutionEngineBatchResultSetFinished);
}
}
public static string BatchParserWrapperExecutionEngineBatchCancelling
{
get
{
return Keys.GetString(Keys.BatchParserWrapperExecutionEngineBatchCancelling);
}
}
public static string EE_ScriptError_Warning
{
get
{
return Keys.GetString(Keys.EE_ScriptError_Warning);
}
}
public static string TroubleshootingAssistanceMessage
{
get
{
return Keys.GetString(Keys.TroubleshootingAssistanceMessage);
}
}
public static string BatchParser_CircularReference
{
get
{
return Keys.GetString(Keys.BatchParser_CircularReference);
}
}
public static string BatchParser_CommentNotTerminated
{
get
{
return Keys.GetString(Keys.BatchParser_CommentNotTerminated);
}
}
public static string BatchParser_StringNotTerminated
{
get
{
return Keys.GetString(Keys.BatchParser_StringNotTerminated);
}
}
public static string BatchParser_IncorrectSyntax
{
get
{
return Keys.GetString(Keys.BatchParser_IncorrectSyntax);
}
}
public static string BatchParser_VariableNotDefined
{
get
{
return Keys.GetString(Keys.BatchParser_VariableNotDefined);
}
}
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Keys
{
static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ManagedBatchParser.Localization.SR", typeof(SR).GetTypeInfo().Assembly);
static CultureInfo _culture = null;
public const string EE_BatchSqlMessageNoProcedureInfo = "EE_BatchSqlMessageNoProcedureInfo";
public const string EE_BatchSqlMessageWithProcedureInfo = "EE_BatchSqlMessageWithProcedureInfo";
public const string EE_BatchSqlMessageNoLineInfo = "EE_BatchSqlMessageNoLineInfo";
public const string EE_BatchError_Exception = "EE_BatchError_Exception";
public const string EE_BatchExecutionInfo_RowsAffected = "EE_BatchExecutionInfo_RowsAffected";
public const string EE_ExecutionNotYetCompleteError = "EE_ExecutionNotYetCompleteError";
public const string EE_ScriptError_Error = "EE_ScriptError_Error";
public const string EE_ScriptError_ParsingSyntax = "EE_ScriptError_ParsingSyntax";
public const string EE_ScriptError_FatalError = "EE_ScriptError_FatalError";
public const string EE_ExecutionInfo_FinalizingLoop = "EE_ExecutionInfo_FinalizingLoop";
public const string EE_ExecutionInfo_QueryCancelledbyUser = "EE_ExecutionInfo_QueryCancelledbyUser";
public const string EE_BatchExecutionError_Halting = "EE_BatchExecutionError_Halting";
public const string EE_BatchExecutionError_Ignoring = "EE_BatchExecutionError_Ignoring";
public const string EE_ExecutionInfo_InitializingLoop = "EE_ExecutionInfo_InitializingLoop";
public const string EE_ExecutionError_CommandNotSupported = "EE_ExecutionError_CommandNotSupported";
public const string EE_ExecutionError_VariableNotFound = "EE_ExecutionError_VariableNotFound";
public const string BatchParserWrapperExecutionEngineError = "BatchParserWrapperExecutionEngineError";
public const string BatchParserWrapperExecutionError = "BatchParserWrapperExecutionError";
public const string BatchParserWrapperExecutionEngineBatchMessage = "BatchParserWrapperExecutionEngineBatchMessage";
public const string BatchParserWrapperExecutionEngineBatchResultSetProcessing = "BatchParserWrapperExecutionEngineBatchResultSetProcessing";
public const string BatchParserWrapperExecutionEngineBatchResultSetFinished = "BatchParserWrapperExecutionEngineBatchResultSetFinished";
public const string BatchParserWrapperExecutionEngineBatchCancelling = "BatchParserWrapperExecutionEngineBatchCancelling";
public const string EE_ScriptError_Warning = "EE_ScriptError_Warning";
public const string TroubleshootingAssistanceMessage = "TroubleshootingAssistanceMessage";
public const string BatchParser_CircularReference = "BatchParser_CircularReference";
public const string BatchParser_CommentNotTerminated = "BatchParser_CommentNotTerminated";
public const string BatchParser_StringNotTerminated = "BatchParser_StringNotTerminated";
public const string BatchParser_IncorrectSyntax = "BatchParser_IncorrectSyntax";
public const string BatchParser_VariableNotDefined = "BatchParser_VariableNotDefined";
private Keys()
{ }
public static CultureInfo Culture
{
get
{
return _culture;
}
set
{
_culture = value;
}
}
public static string GetString(string key)
{
return resourceManager.GetString(key, _culture);
}
}
}
}

View File

@@ -0,0 +1,236 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype=">text/microsoft-resx</resheader>
<resheader name="version=">2.0</resheader>
<resheader name="reader=">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer=">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1="><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing=">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64=">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64=">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata=">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true=">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded=">
<xsd:element name="metadata=">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly=">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data=">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader=">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="EE_BatchSqlMessageNoProcedureInfo" xml:space="preserve">
<value>Msg {0}, Level {1}, State {2}, Line {3}</value>
<comment></comment>
</data>
<data name="EE_BatchSqlMessageWithProcedureInfo" xml:space="preserve">
<value>Msg {0}, Level {1}, State {2}, Procedure {3}, Line {4}</value>
<comment></comment>
</data>
<data name="EE_BatchSqlMessageNoLineInfo" xml:space="preserve">
<value>Msg {0}, Level {1}, State {2}</value>
<comment></comment>
</data>
<data name="EE_BatchError_Exception" xml:space="preserve">
<value>An error occurred while the batch was being processed. The error message is: {0}</value>
<comment></comment>
</data>
<data name="EE_BatchExecutionInfo_RowsAffected" xml:space="preserve">
<value>({0} row(s) affected)</value>
<comment></comment>
</data>
<data name="EE_ExecutionNotYetCompleteError" xml:space="preserve">
<value>The previous execution is not yet complete.</value>
<comment></comment>
</data>
<data name="EE_ScriptError_Error" xml:space="preserve">
<value>A scripting error occurred.</value>
<comment></comment>
</data>
<data name="EE_ScriptError_ParsingSyntax" xml:space="preserve">
<value>Incorrect syntax was encountered while {0} was being parsed.</value>
<comment></comment>
</data>
<data name="EE_ScriptError_FatalError" xml:space="preserve">
<value>A fatal error occurred.</value>
<comment></comment>
</data>
<data name="EE_ExecutionInfo_FinalizingLoop" xml:space="preserve">
<value>Batch execution completed {0} times...</value>
<comment></comment>
</data>
<data name="EE_ExecutionInfo_QueryCancelledbyUser" xml:space="preserve">
<value>You cancelled the query.</value>
<comment></comment>
</data>
<data name="EE_BatchExecutionError_Halting" xml:space="preserve">
<value>An error occurred while the batch was being executed.</value>
<comment></comment>
</data>
<data name="EE_BatchExecutionError_Ignoring" xml:space="preserve">
<value>An error occurred while the batch was being executed, but the error has been ignored.</value>
<comment></comment>
</data>
<data name="EE_ExecutionInfo_InitializingLoop" xml:space="preserve">
<value>Beginning execution loop</value>
<comment></comment>
</data>
<data name="EE_ExecutionError_CommandNotSupported" xml:space="preserve">
<value>Command {0} is not supported.</value>
<comment></comment>
</data>
<data name="EE_ExecutionError_VariableNotFound" xml:space="preserve">
<value>The variable {0} could not be found.</value>
<comment></comment>
</data>
<data name="BatchParserWrapperExecutionEngineError" xml:space="preserve">
<value>SQL Execution error: {0}</value>
<comment></comment>
</data>
<data name="BatchParserWrapperExecutionError" xml:space="preserve">
<value>Batch parser wrapper execution: {0} found... at line {1}: {2} Description: {3}</value>
<comment></comment>
</data>
<data name="BatchParserWrapperExecutionEngineBatchMessage" xml:space="preserve">
<value>Batch parser wrapper execution engine batch message received: Message: {0} Detailed message: {1}</value>
<comment></comment>
</data>
<data name="BatchParserWrapperExecutionEngineBatchResultSetProcessing" xml:space="preserve">
<value>Batch parser wrapper execution engine batch ResultSet processing: DataReader.FieldCount: {0} DataReader.RecordsAffected: {1}</value>
<comment></comment>
</data>
<data name="BatchParserWrapperExecutionEngineBatchResultSetFinished" xml:space="preserve">
<value>Batch parser wrapper execution engine batch ResultSet finished.</value>
<comment></comment>
</data>
<data name="BatchParserWrapperExecutionEngineBatchCancelling" xml:space="preserve">
<value>Canceling batch parser wrapper batch execution.</value>
<comment></comment>
</data>
<data name="EE_ScriptError_Warning" xml:space="preserve">
<value>Scripting warning.</value>
<comment></comment>
</data>
<data name="TroubleshootingAssistanceMessage" xml:space="preserve">
<value>For more information about this error, see the troubleshooting topics in the product documentation.</value>
<comment></comment>
</data>
<data name="BatchParser_CircularReference" xml:space="preserve">
<value>File '{0}' recursively included.</value>
<comment></comment>
</data>
<data name="BatchParser_CommentNotTerminated" xml:space="preserve">
<value>Missing end comment mark '*/'.</value>
<comment></comment>
</data>
<data name="BatchParser_StringNotTerminated" xml:space="preserve">
<value>Unclosed quotation mark after the character string.</value>
<comment></comment>
</data>
<data name="BatchParser_IncorrectSyntax" xml:space="preserve">
<value>Incorrect syntax was encountered while parsing '{0}'.</value>
<comment></comment>
</data>
<data name="BatchParser_VariableNotDefined" xml:space="preserve">
<value>Variable {0} is not defined.</value>
<comment></comment>
</data>
</root>

View File

@@ -0,0 +1,84 @@
# String resource file
#
# When processed by the String Resource Tool, this file generates
# both a .CS and a .RESX file with the same name as the file.
# The .CS file contains a class which can be used to access these
# string resources, including the ability to format in
# parameters, which are identified with the .NET {x} format
# (see String.Format help).
#
# Comments below assume the file name is SR.strings.
#
# Lines starting with a semicolon ";" are also treated as comments, but
# in a future version they will be extracted and made available in LocStudio
# Put your comments to localizers _before_ the string they apply to.
#
# SMO build specific comment
# after generating the .resx file, run srgen on it and get the .resx file
# please remember to also check that .resx in, along with the
# .strings and .cs files
[strings]
############################################################################
# DacFx Resources
EE_BatchSqlMessageNoProcedureInfo = Msg {0}, Level {1}, State {2}, Line {3}
EE_BatchSqlMessageWithProcedureInfo = Msg {0}, Level {1}, State {2}, Procedure {3}, Line {4}
EE_BatchSqlMessageNoLineInfo = Msg {0}, Level {1}, State {2}
EE_BatchError_Exception = An error occurred while the batch was being processed. The error message is: {0}
EE_BatchExecutionInfo_RowsAffected = ({0} row(s) affected)
EE_ExecutionNotYetCompleteError = The previous execution is not yet complete.
EE_ScriptError_Error = A scripting error occurred.
EE_ScriptError_ParsingSyntax = Incorrect syntax was encountered while {0} was being parsed.
EE_ScriptError_FatalError = A fatal error occurred.
EE_ExecutionInfo_FinalizingLoop = Batch execution completed {0} times...
EE_ExecutionInfo_QueryCancelledbyUser = You cancelled the query.
EE_BatchExecutionError_Halting = An error occurred while the batch was being executed.
EE_BatchExecutionError_Ignoring = An error occurred while the batch was being executed, but the error has been ignored.
EE_ExecutionInfo_InitializingLoop = Beginning execution loop
EE_ExecutionError_CommandNotSupported = Command {0} is not supported.
EE_ExecutionError_VariableNotFound = The variable {0} could not be found.
BatchParserWrapperExecutionEngineError = SQL Execution error: {0}
BatchParserWrapperExecutionError = Batch parser wrapper execution: {0} found... at line {1}: {2} Description: {3}
BatchParserWrapperExecutionEngineBatchMessage = Batch parser wrapper execution engine batch message received: Message: {0} Detailed message: {1}
BatchParserWrapperExecutionEngineBatchResultSetProcessing = Batch parser wrapper execution engine batch ResultSet processing: DataReader.FieldCount: {0} DataReader.RecordsAffected: {1}
BatchParserWrapperExecutionEngineBatchResultSetFinished = Batch parser wrapper execution engine batch ResultSet finished.
BatchParserWrapperExecutionEngineBatchCancelling = Canceling batch parser wrapper batch execution.
EE_ScriptError_Warning = Scripting warning.
TroubleshootingAssistanceMessage = For more information about this error, see the troubleshooting topics in the product documentation.
BatchParser_CircularReference = File '{0}' recursively included.
BatchParser_CommentNotTerminated = Missing end comment mark '*/'.
BatchParser_StringNotTerminated = Unclosed quotation mark after the character string.
BatchParser_IncorrectSyntax = Incorrect syntax was encountered while parsing '{0}'.
BatchParser_VariableNotDefined = Variable {0} is not defined.

View File

@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" original="sr.resx" source-language="en">
<body>
<trans-unit id="EE_BatchSqlMessageNoProcedureInfo">
<source>Msg {0}, Level {1}, State {2}, Line {3}</source>
<target state="new">Msg {0}, Level {1}, State {2}, Line {3}</target>
<note></note>
</trans-unit>
<trans-unit id="EE_BatchSqlMessageWithProcedureInfo">
<source>Msg {0}, Level {1}, State {2}, Procedure {3}, Line {4}</source>
<target state="new">Msg {0}, Level {1}, State {2}, Procedure {3}, Line {4}</target>
<note></note>
</trans-unit>
<trans-unit id="EE_BatchSqlMessageNoLineInfo">
<source>Msg {0}, Level {1}, State {2}</source>
<target state="new">Msg {0}, Level {1}, State {2}</target>
<note></note>
</trans-unit>
<trans-unit id="EE_BatchError_Exception">
<source>An error occurred while the batch was being processed. The error message is: {0}</source>
<target state="new">An error occurred while the batch was being processed. The error message is: {0}</target>
<note></note>
</trans-unit>
<trans-unit id="EE_BatchExecutionInfo_RowsAffected">
<source>({0} row(s) affected)</source>
<target state="new">({0} row(s) affected)</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ExecutionNotYetCompleteError">
<source>The previous execution is not yet complete.</source>
<target state="new">The previous execution is not yet complete.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ScriptError_Error">
<source>A scripting error occurred.</source>
<target state="new">A scripting error occurred.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ScriptError_ParsingSyntax">
<source>Incorrect syntax was encountered while {0} was being parsed.</source>
<target state="new">Incorrect syntax was encountered while {0} was being parsed.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ScriptError_FatalError">
<source>A fatal error occurred.</source>
<target state="new">A fatal error occurred.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ExecutionInfo_FinalizingLoop">
<source>Batch execution completed {0} times...</source>
<target state="new">Batch execution completed {0} times...</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ExecutionInfo_QueryCancelledbyUser">
<source>You cancelled the query.</source>
<target state="new">You cancelled the query.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_BatchExecutionError_Halting">
<source>An error occurred while the batch was being executed.</source>
<target state="new">An error occurred while the batch was being executed.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_BatchExecutionError_Ignoring">
<source>An error occurred while the batch was being executed, but the error has been ignored.</source>
<target state="new">An error occurred while the batch was being executed, but the error has been ignored.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ExecutionInfo_InitializingLoop">
<source>Beginning execution loop</source>
<target state="new">Beginning execution loop</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ExecutionError_CommandNotSupported">
<source>Command {0} is not supported.</source>
<target state="new">Command {0} is not supported.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ExecutionError_VariableNotFound">
<source>The variable {0} could not be found.</source>
<target state="new">The variable {0} could not be found.</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParserWrapperExecutionEngineError">
<source>SQL Execution error: {0}</source>
<target state="new">SQL Execution error: {0}</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParserWrapperExecutionError">
<source>Batch parser wrapper execution: {0} found... at line {1}: {2} Description: {3}</source>
<target state="new">Batch parser wrapper execution: {0} found... at line {1}: {2} Description: {3}</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParserWrapperExecutionEngineBatchMessage">
<source>Batch parser wrapper execution engine batch message received: Message: {0} Detailed message: {1}</source>
<target state="new">Batch parser wrapper execution engine batch message received: Message: {0} Detailed message: {1}</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParserWrapperExecutionEngineBatchResultSetProcessing">
<source>Batch parser wrapper execution engine batch ResultSet processing: DataReader.FieldCount: {0} DataReader.RecordsAffected: {1}</source>
<target state="new">Batch parser wrapper execution engine batch ResultSet processing: DataReader.FieldCount: {0} DataReader.RecordsAffected: {1}</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParserWrapperExecutionEngineBatchResultSetFinished">
<source>Batch parser wrapper execution engine batch ResultSet finished.</source>
<target state="new">Batch parser wrapper execution engine batch ResultSet finished.</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParserWrapperExecutionEngineBatchCancelling">
<source>Canceling batch parser wrapper batch execution.</source>
<target state="new">Canceling batch parser wrapper batch execution.</target>
<note></note>
</trans-unit>
<trans-unit id="EE_ScriptError_Warning">
<source>Scripting warning.</source>
<target state="new">Scripting warning.</target>
<note></note>
</trans-unit>
<trans-unit id="TroubleshootingAssistanceMessage">
<source>For more information about this error, see the troubleshooting topics in the product documentation.</source>
<target state="new">For more information about this error, see the troubleshooting topics in the product documentation.</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParser_CircularReference">
<source>File '{0}' recursively included.</source>
<target state="new">File '{0}' recursively included.</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParser_CommentNotTerminated">
<source>Missing end comment mark '*/'.</source>
<target state="new">Missing end comment mark '*/'.</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParser_StringNotTerminated">
<source>Unclosed quotation mark after the character string.</source>
<target state="new">Unclosed quotation mark after the character string.</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParser_IncorrectSyntax">
<source>Incorrect syntax was encountered while parsing '{0}'.</source>
<target state="new">Incorrect syntax was encountered while parsing '{0}'.</target>
<note></note>
</trans-unit>
<trans-unit id="BatchParser_VariableNotDefined">
<source>Variable {0} is not defined.</source>
<target state="new">Variable {0} is not defined.</target>
<note></note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
</PropertyGroup>
<ItemGroup>
<Folder Include="Localization\transXliff\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" Version="150.18040.0-preview" />
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Localization\sr.resx" />
<None Include="Localization\sr.strings" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,43 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Microsoft.SqlTools.ManagedBatchParser")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Microsoft.SqlTools.ManagedBatchParser")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("82dd9738-2ad3-4eb3-9f80-18b594e03621")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.UnitTests")]
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.IntegrationTests")]
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.Test.Common")]
[assembly: InternalsVisibleTo("MicrosoftSqlToolsServiceLayer")]
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ManagedBatchParser.UnitTests")]

View File

@@ -0,0 +1,453 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// This class represents connection (and other) settings specified by called of the DacFx API. DacFx
/// cannot rely on the registry to supply override values therefore setting overrides must be made
/// by the top-of-the-stack
/// </summary>
public sealed class AmbientSettings
{
private const string LogicalContextName = "__LocalContextConfigurationName";
public enum StreamBackingStore
{
// MemoryStream
Memory = 0,
// FileStream
File = 1
}
// Internal for test purposes
internal const string MasterReferenceFilePathIndex = "MasterReferenceFilePath";
internal const string DatabaseLockTimeoutIndex = "DatabaseLockTimeout";
internal const string QueryTimeoutIndex = "QueryTimeout";
internal const string LongRunningQueryTimeoutIndex = "LongRunningQueryTimeout";
internal const string AlwaysRetryOnTransientFailureIndex = "AlwaysRetryOnTransientFailure";
internal const string MaxDataReaderDegreeOfParallelismIndex = "MaxDataReaderDegreeOfParallelism";
internal const string ConnectionRetryHandlerIndex = "ConnectionRetryHandler";
internal const string TraceRowCountFailureIndex = "TraceRowCountFailure";
internal const string TableProgressUpdateIntervalIndex = "TableProgressUpdateInterval";
internal const string UseOfflineDataReaderIndex = "UseOfflineDataReader";
internal const string StreamBackingStoreForOfflineDataReadingIndex = "StreamBackingStoreForOfflineDataReading";
internal const string DisableIndexesForDataPhaseIndex = "DisableIndexesForDataPhase";
internal const string ReliableDdlEnabledIndex = "ReliableDdlEnabled";
internal const string ImportModelDatabaseIndex = "ImportModelDatabase";
internal const string SupportAlwaysEncryptedIndex = "SupportAlwaysEncrypted";
internal const string SkipObjectTypeBlockingIndex = "SkipObjectTypeBlocking";
internal const string DoNotSerializeQueryStoreSettingsIndex = "DoNotSerializeQueryStoreSettings";
internal const string AlwaysEncryptedWizardMigrationIndex = "AlwaysEncryptedWizardMigration";
public static AmbientData _defaultSettings;
static AmbientSettings()
{
_defaultSettings = new AmbientData();
}
/// <summary>
/// Access to the default ambient settings. Access to these settings is made available
/// for SSDT scenarios where settings are read from the registry and not set explicitly through
/// the API
/// </summary>
public static AmbientData DefaultSettings
{
get { return _defaultSettings; }
}
public static string MasterReferenceFilePath
{
get { return GetValue<string>(MasterReferenceFilePathIndex); }
}
public static int LockTimeoutMilliSeconds
{
get { return GetValue<int>(DatabaseLockTimeoutIndex); }
}
public static int QueryTimeoutSeconds
{
get { return GetValue<int>(QueryTimeoutIndex); }
}
public static int LongRunningQueryTimeoutSeconds
{
get { return GetValue<int>(LongRunningQueryTimeoutIndex); }
}
public static Action<SqlServerRetryError> ConnectionRetryMessageHandler
{
get { return GetValue<Action<SqlServerRetryError>>(ConnectionRetryHandlerIndex); }
}
public static bool AlwaysRetryOnTransientFailure
{
get { return GetValue<bool>(AlwaysRetryOnTransientFailureIndex); }
}
public static int MaxDataReaderDegreeOfParallelism
{
get { return GetValue<int>(MaxDataReaderDegreeOfParallelismIndex); }
}
public static int TableProgressUpdateInterval
{
// value of zero means do not fire 'heartbeat' progress events. Non-zero values will
// fire a heartbeat progress event every n seconds.
get { return GetValue<int>(TableProgressUpdateIntervalIndex); }
}
public static bool TraceRowCountFailure
{
get { return GetValue<bool>(TraceRowCountFailureIndex); }
}
public static bool UseOfflineDataReader
{
get { return GetValue<bool>(UseOfflineDataReaderIndex); }
}
public static StreamBackingStore StreamBackingStoreForOfflineDataReading
{
get { return GetValue<StreamBackingStore>(StreamBackingStoreForOfflineDataReadingIndex); }
}
public static bool DisableIndexesForDataPhase
{
get { return GetValue<bool>(DisableIndexesForDataPhaseIndex); }
}
public static bool ReliableDdlEnabled
{
get { return GetValue<bool>(ReliableDdlEnabledIndex); }
}
public static bool ImportModelDatabase
{
get { return GetValue<bool>(ImportModelDatabaseIndex); }
}
/// <summary>
/// Setting that shows whether Always Encrypted is supported.
/// If false, then reverse engineering and script interpretation of a database with any Always Encrypted object will fail
/// </summary>
public static bool SupportAlwaysEncrypted
{
get { return GetValue<bool>(SupportAlwaysEncryptedIndex); }
}
public static bool AlwaysEncryptedWizardMigration
{
get { return GetValue<bool>(AlwaysEncryptedWizardMigrationIndex); }
}
/// <summary>
/// Setting that determines whether checks for unsupported object types are performed.
/// If false, unsupported object types will prevent extract from being performed.
/// Default value is false.
/// </summary>
public static bool SkipObjectTypeBlocking
{
get { return GetValue<bool>(SkipObjectTypeBlockingIndex); }
}
/// <summary>
/// Setting that determines whether the Database Options that store Query Store settings will be left out during package serialization.
/// Default value is false.
/// </summary>
public static bool DoNotSerializeQueryStoreSettings
{
get { return GetValue<bool>(DoNotSerializeQueryStoreSettingsIndex); }
}
/// <summary>
/// Called by top-of-stack API to setup/configure settings that should be used
/// throughout the API (lower in the stack). The settings are reverted once the returned context
/// has been disposed.
/// </summary>
public static IStackSettingsContext CreateSettingsContext()
{
return new StackConfiguration();
}
private static T1 GetValue<T1>(string configIndex)
{
IAmbientDataDirectAccess config = _defaultSettings;
return (T1)config.Data[configIndex].Value;
}
/// <summary>
/// Data-transfer object that represents a specific configuration
/// </summary>
public class AmbientData : IAmbientDataDirectAccess
{
private readonly Dictionary<string, AmbientValue> _configuration;
public AmbientData()
{
_configuration = new Dictionary<string, AmbientValue>(StringComparer.OrdinalIgnoreCase);
_configuration[DatabaseLockTimeoutIndex] = new AmbientValue(5000);
_configuration[QueryTimeoutIndex] = new AmbientValue(60);
_configuration[LongRunningQueryTimeoutIndex] = new AmbientValue(0);
_configuration[AlwaysRetryOnTransientFailureIndex] = new AmbientValue(false);
_configuration[ConnectionRetryHandlerIndex] = new AmbientValue(typeof(Action<SqlServerRetryError>), null);
_configuration[MaxDataReaderDegreeOfParallelismIndex] = new AmbientValue(8);
_configuration[TraceRowCountFailureIndex] = new AmbientValue(false); // default: throw DacException on rowcount mismatch during import/export data validation
_configuration[TableProgressUpdateIntervalIndex] = new AmbientValue(300); // default: fire heartbeat progress update events every 5 minutes
_configuration[UseOfflineDataReaderIndex] = new AmbientValue(false);
_configuration[StreamBackingStoreForOfflineDataReadingIndex] = new AmbientValue(StreamBackingStore.File); //applicable only when UseOfflineDataReader is set to true
_configuration[MasterReferenceFilePathIndex] = new AmbientValue(typeof(string), null);
// Defect 1210884: Enable an option to allow secondary index, check and fk constraints to stay enabled during data upload with import in DACFX for IES
_configuration[DisableIndexesForDataPhaseIndex] = new AmbientValue(true);
_configuration[ReliableDdlEnabledIndex] = new AmbientValue(false);
_configuration[ImportModelDatabaseIndex] = new AmbientValue(true);
_configuration[SupportAlwaysEncryptedIndex] = new AmbientValue(false);
_configuration[AlwaysEncryptedWizardMigrationIndex] = new AmbientValue(false);
_configuration[SkipObjectTypeBlockingIndex] = new AmbientValue(false);
_configuration[DoNotSerializeQueryStoreSettingsIndex] = new AmbientValue(false);
}
public string MasterReferenceFilePath
{
get { return (string)_configuration[MasterReferenceFilePathIndex].Value; }
set { _configuration[MasterReferenceFilePathIndex].Value = value; }
}
public int LockTimeoutMilliSeconds
{
get { return (int)_configuration[DatabaseLockTimeoutIndex].Value; }
set { _configuration[DatabaseLockTimeoutIndex].Value = value; }
}
public int QueryTimeoutSeconds
{
get { return (int)_configuration[QueryTimeoutIndex].Value; }
set { _configuration[QueryTimeoutIndex].Value = value; }
}
public int LongRunningQueryTimeoutSeconds
{
get { return (int)_configuration[LongRunningQueryTimeoutIndex].Value; }
set { _configuration[LongRunningQueryTimeoutIndex].Value = value; }
}
public bool AlwaysRetryOnTransientFailure
{
get { return (bool)_configuration[AlwaysRetryOnTransientFailureIndex].Value; }
set { _configuration[AlwaysRetryOnTransientFailureIndex].Value = value; }
}
public Action<SqlServerRetryError> ConnectionRetryMessageHandler
{
get { return (Action<SqlServerRetryError>)_configuration[ConnectionRetryHandlerIndex].Value; }
set { _configuration[ConnectionRetryHandlerIndex].Value = value; }
}
public bool TraceRowCountFailure
{
get { return (bool)_configuration[TraceRowCountFailureIndex].Value; }
set { _configuration[TraceRowCountFailureIndex].Value = value; }
}
public int TableProgressUpdateInterval
{
get { return (int)_configuration[TableProgressUpdateIntervalIndex].Value; }
set { _configuration[TableProgressUpdateIntervalIndex].Value = value; }
}
public bool UseOfflineDataReader
{
get { return (bool)_configuration[UseOfflineDataReaderIndex].Value; }
set { _configuration[UseOfflineDataReaderIndex].Value = value; }
}
public StreamBackingStore StreamBackingStoreForOfflineDataReading
{
get { return (StreamBackingStore)_configuration[StreamBackingStoreForOfflineDataReadingIndex].Value; }
set { _configuration[StreamBackingStoreForOfflineDataReadingIndex].Value = value; }
}
public bool DisableIndexesForDataPhase
{
get { return (bool)_configuration[DisableIndexesForDataPhaseIndex].Value; }
set { _configuration[DisableIndexesForDataPhaseIndex].Value = value; }
}
public bool ReliableDdlEnabled
{
get { return (bool)_configuration[ReliableDdlEnabledIndex].Value; }
set { _configuration[ReliableDdlEnabledIndex].Value = value; }
}
public bool ImportModelDatabase
{
get { return (bool)_configuration[ImportModelDatabaseIndex].Value; }
set { _configuration[ImportModelDatabaseIndex].Value = value; }
}
public bool SupportAlwaysEncrypted
{
get { return (bool)_configuration[SupportAlwaysEncryptedIndex].Value; }
set { _configuration[SupportAlwaysEncryptedIndex].Value = value; }
}
public bool AlwaysEncryptedWizardMigration
{
get { return (bool)_configuration[AlwaysEncryptedWizardMigrationIndex].Value; }
set { _configuration[AlwaysEncryptedWizardMigrationIndex].Value = value; }
}
public bool SkipObjectTypeBlocking
{
get { return (bool)_configuration[SkipObjectTypeBlockingIndex].Value; }
set { _configuration[SkipObjectTypeBlockingIndex].Value = value; }
}
public bool DoNotSerializeQueryStoreSettings
{
get { return (bool)_configuration[DoNotSerializeQueryStoreSettingsIndex].Value; }
set { _configuration[DoNotSerializeQueryStoreSettingsIndex].Value = value; }
}
/// <summary>
/// Provides a way to bulk populate settings from a dictionary
/// </summary>
public void PopulateSettings(IDictionary<string, object> settingsCollection)
{
if (settingsCollection != null)
{
Dictionary<string, object> newSettings = new Dictionary<string, object>();
// We know all the values are set on the current configuration
foreach (KeyValuePair<string, object> potentialPair in settingsCollection)
{
AmbientValue currentValue;
if (_configuration.TryGetValue(potentialPair.Key, out currentValue))
{
object newValue = potentialPair.Value;
newSettings[potentialPair.Key] = newValue;
}
}
if (newSettings.Count > 0)
{
foreach (KeyValuePair<string, object> newSetting in newSettings)
{
_configuration[newSetting.Key].Value = newSetting.Value;
}
}
}
}
/// <summary>
/// Logs the Ambient Settings
/// </summary>
public void TraceSettings()
{
// NOTE: logging as warning so we can get this data in the IEService DacFx logs
Logger.Write(TraceEventType.Warning, Resources.LoggingAmbientSettings);
foreach (KeyValuePair<string, AmbientValue> setting in _configuration)
{
// Log Ambient Settings
Logger.Write(
TraceEventType.Warning,
string.Format(
Resources.AmbientSettingFormat,
setting.Key,
setting.Value == null ? setting.Value : setting.Value.Value));
}
}
Dictionary<string, AmbientValue> IAmbientDataDirectAccess.Data
{
get { return _configuration; }
}
}
/// <summary>
/// This class is used as value in the dictionary to ensure that the type of value is correct.
/// </summary>
private class AmbientValue
{
private readonly Type _type;
private readonly bool _isTypeNullable;
private object _value;
public AmbientValue(object value)
: this(value == null ? null : value.GetType(), value)
{
}
public AmbientValue(Type type, object value)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
_type = type;
_isTypeNullable = !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null;
Value = value;
}
public object Value
{
get { return _value; }
set
{
if ((_isTypeNullable && value == null) || _type.GetTypeInfo().IsInstanceOfType(value))
{
_value = value;
}
else
{
Logger.Write(TraceEventType.Error, string.Format(Resources.UnableToAssignValue, value.GetType().FullName, _type.FullName));
}
}
}
}
/// <summary>
/// This private interface allows pass-through access directly to member data
/// </summary>
private interface IAmbientDataDirectAccess
{
Dictionary<string, AmbientValue> Data { get; }
}
/// <summary>
/// This class encapsulated the concept of configuration that is set on the stack and
/// flows across multiple threads as part of the logical call context
/// </summary>
private sealed class StackConfiguration : IStackSettingsContext
{
private readonly AmbientData _data;
public StackConfiguration()
{
_data = new AmbientData();
//CallContext.LogicalSetData(LogicalContextName, _data);
}
public AmbientData Settings
{
get { return _data; }
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
//CallContext.LogicalSetData(LogicalContextName, null);
}
}
}
}

View File

@@ -0,0 +1,285 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Concurrent;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// This class caches server information for subsequent use
/// </summary>
public class CachedServerInfo
{
/// <summary>
/// Singleton service instance
/// </summary>
private static readonly Lazy<CachedServerInfo> instance
= new Lazy<CachedServerInfo>(() => new CachedServerInfo());
/// <summary>
/// Gets the singleton instance
/// </summary>
public static CachedServerInfo Instance
{
get
{
return instance.Value;
}
}
public enum CacheVariable {
IsSqlDw,
IsAzure,
IsCloud
}
#region CacheKey implementation
internal class CacheKey : IEquatable<CacheKey>
{
private string dataSource;
private string dbName;
public CacheKey(SqlConnectionStringBuilder builder)
{
Validate.IsNotNull(nameof(builder), builder);
dataSource = builder.DataSource;
dbName = GetDatabaseName(builder);
}
internal static string GetDatabaseName(SqlConnectionStringBuilder builder)
{
string dbName = string.Empty;
if (!string.IsNullOrEmpty((builder.InitialCatalog)))
{
dbName = builder.InitialCatalog;
}
else if (!string.IsNullOrEmpty((builder.AttachDBFilename)))
{
dbName = builder.AttachDBFilename;
}
return dbName;
}
public override bool Equals(object obj)
{
if (obj == null) { return false; }
CacheKey keyObj = obj as CacheKey;
if (keyObj == null) { return false; }
else { return Equals(keyObj); }
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = (hash * 23) + (dataSource != null ? dataSource.GetHashCode() : 0);
hash = (hash * 23) + (dbName != null ? dbName.GetHashCode() : 0);
return hash;
}
}
public bool Equals(CacheKey other)
{
return string.Equals(dataSource, other.dataSource, StringComparison.OrdinalIgnoreCase)
&& string.Equals(dbName, other.dbName, StringComparison.OrdinalIgnoreCase);
}
}
#endregion
private struct CachedInfo
{
public bool IsAzure;
public DateTime LastUpdate;
public bool IsSqlDw;
}
private const int _maxCacheSize = 1024;
private const int _deleteBatchSize = 512;
private const int MinimalQueryTimeoutSecondsForAzure = 300;
private ConcurrentDictionary<CacheKey, CachedInfo> _cache;
private object _cacheLock;
/// <summary>
/// Internal constructor for testing purposes. For all code use, please use the <see cref="CachedServerInfo.Instance"/>
/// default instance.
/// </summary>
public CachedServerInfo()
{
_cache = new ConcurrentDictionary<CacheKey, CachedInfo>();
_cacheLock = new object();
}
public int GetQueryTimeoutSeconds(IDbConnection connection)
{
SqlConnectionStringBuilder connStringBuilder = SafeGetConnectionStringFromConnection(connection);
return GetQueryTimeoutSeconds(connStringBuilder);
}
public int GetQueryTimeoutSeconds(SqlConnectionStringBuilder builder)
{
//keep existing behavior and return the default ambient settings
//if the provided data source is null or whitespace, or the original
//setting is already 0 which means no limit.
int originalValue = AmbientSettings.QueryTimeoutSeconds;
if (builder == null || string.IsNullOrWhiteSpace(builder.DataSource)
|| (originalValue == 0))
{
return originalValue;
}
CachedInfo info;
bool hasFound = TryGetCacheValue(builder, out info);
if (hasFound && info.IsAzure
&& originalValue < MinimalQueryTimeoutSecondsForAzure)
{
return MinimalQueryTimeoutSecondsForAzure;
}
else
{
return originalValue;
}
}
public void AddOrUpdateIsCloud(IDbConnection connection, bool isCloud)
{
AddOrUpdateCache(connection, isCloud, CacheVariable.IsCloud);
}
public void AddOrUpdateIsAzure(IDbConnection connection, bool isAzure)
{
AddOrUpdateCache(connection, isAzure, CacheVariable.IsAzure);
}
public void AddOrUpdateIsSqlDw(IDbConnection connection, bool isSqlDw)
{
AddOrUpdateCache(connection, isSqlDw, CacheVariable.IsSqlDw);
}
private void AddOrUpdateCache(IDbConnection connection, bool newState, CacheVariable cacheVar)
{
Validate.IsNotNull(nameof(connection), connection);
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
AddOrUpdateCache(builder, newState, cacheVar);
}
public void AddOrUpdateCache(SqlConnectionStringBuilder builder, bool newState, CacheVariable cacheVar)
{
Validate.IsNotNull(nameof(builder), builder);
Validate.IsNotNullOrWhitespaceString(nameof(builder) + ".DataSource", builder.DataSource);
CachedInfo info;
bool hasFound = TryGetCacheValue(builder, out info);
if ((cacheVar == CacheVariable.IsSqlDw && hasFound && info.IsSqlDw == newState) ||
(cacheVar == CacheVariable.IsAzure && hasFound && info.IsAzure == newState))
{
// No change needed
return;
}
else
{
lock (_cacheLock)
{
// Clean older keys, update info, and add this back into the cache
CacheKey key = new CacheKey(builder);
CleanupCache(key);
if (cacheVar == CacheVariable.IsSqlDw)
{
info.IsSqlDw = newState;
}
else if (cacheVar == CacheVariable.IsAzure)
{
info.IsAzure = newState;
}
info.LastUpdate = DateTime.UtcNow;
_cache.AddOrUpdate(key, info, (k, oldValue) => info);
}
}
}
private void CleanupCache(CacheKey newKey)
{
if (!_cache.ContainsKey(newKey))
{
//delete a batch of old elements when we try to add a new one and
//the capacity limitation is hit
if (_cache.Keys.Count > _maxCacheSize - 1)
{
var keysToDelete = _cache
.OrderBy(x => x.Value.LastUpdate)
.Take(_deleteBatchSize)
.Select(pair => pair.Key);
foreach (CacheKey key in keysToDelete)
{
CachedInfo info;
_cache.TryRemove(key, out info);
}
}
}
}
public bool TryGetIsSqlDw(IDbConnection connection, out bool isSqlDw)
{
Validate.IsNotNull(nameof(connection), connection);
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
return TryGetIsSqlDw(builder, out isSqlDw);
}
public bool TryGetIsSqlDw(SqlConnectionStringBuilder builder, out bool isSqlDw)
{
Validate.IsNotNull(nameof(builder), builder);
Validate.IsNotNullOrWhitespaceString(nameof(builder) + ".DataSource", builder.DataSource);
CachedInfo info;
bool hasFound = TryGetCacheValue(builder, out info);
if(hasFound)
{
isSqlDw = info.IsSqlDw;
return true;
}
isSqlDw = false;
return false;
}
private static SqlConnectionStringBuilder SafeGetConnectionStringFromConnection(IDbConnection connection)
{
if (connection == null)
{
return null;
}
try
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
return builder;
}
catch
{
Logger.Write(TraceEventType.Error, String.Format(Resources.FailedToParseConnectionString, connection.ConnectionString));
return null;
}
}
private bool TryGetCacheValue(SqlConnectionStringBuilder builder, out CachedInfo value)
{
CacheKey key = new CacheKey(builder);
return _cache.TryGetValue(key, out value);
}
}
}

View File

@@ -0,0 +1,17 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Contains common constants used throughout ReliableConnection code.
/// </summary>
internal static class Constants
{
internal const int UndefinedErrorCode = 0;
internal const string Local = "(local)";
}
}

View File

@@ -0,0 +1,214 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Globalization;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// This class is used to encapsulate all the information needed by the DataSchemaErrorTaskService to create a corresponding entry in the Visual Studio Error List.
/// A component should add this Error Object to the <see cref="ErrorManager"/> for such purpose.
/// Errors and their children are expected to be thread-safe. Ideally, this means that
/// the objects are just data-transfer-objects initialized during construction.
/// </summary>
[Serializable]
public class DataSchemaError
{
internal const string DefaultPrefix = "SQL";
private const int MaxErrorCode = 99999;
protected const int UndefinedErrorCode = 0;
public DataSchemaError() : this(string.Empty, ErrorSeverity.Unknown)
{
}
public DataSchemaError(string message, ErrorSeverity severity)
: this(message, string.Empty, severity)
{
}
public DataSchemaError(string message, Exception innerException, ErrorSeverity severity)
: this(message, innerException, string.Empty, 0, severity)
{
}
public DataSchemaError(string message, string document, ErrorSeverity severity)
: this(message, document, 0, 0, DefaultPrefix, UndefinedErrorCode, severity)
{
}
public DataSchemaError(string message, string document, int errorCode, ErrorSeverity severity)
: this(message, document, 0, 0, DefaultPrefix, errorCode, severity)
{
}
public DataSchemaError(string message, string document, int line, int column, ErrorSeverity severity)
: this(message, document,line, column, DefaultPrefix, UndefinedErrorCode, severity)
{
}
public DataSchemaError(DataSchemaError source, ErrorSeverity severity)
: this(source.Message, source.Document, source.Line, source.Column, source.Prefix, source.ErrorCode, severity)
{
}
public DataSchemaError(
Exception exception,
string prefix,
int errorCode,
ErrorSeverity severity)
: this(exception, string.Empty, 0, 0, prefix, errorCode, severity)
{
}
public DataSchemaError(
string message,
Exception exception,
string prefix,
int errorCode,
ErrorSeverity severity)
: this(message, exception, string.Empty, 0, 0, prefix, errorCode, severity)
{
}
public DataSchemaError(
Exception exception,
string document,
int line,
int column,
string prefix,
int errorCode,
ErrorSeverity severity)
: this(exception.Message, exception, document, line, column, prefix, errorCode, severity)
{
}
public DataSchemaError(
string message,
string document,
int line,
int column,
string prefix,
int errorCode,
ErrorSeverity severity)
: this(message, null, document, line, column, prefix, errorCode, severity)
{
}
public DataSchemaError(
string message,
Exception exception,
string document,
int line,
int column,
string prefix,
int errorCode,
ErrorSeverity severity)
{
if (errorCode > MaxErrorCode || errorCode < 0)
{
throw new ArgumentOutOfRangeException("errorCode");
}
Document = document;
Severity = severity;
Line = line;
Column = column;
Message = message;
Exception = exception;
ErrorCode = errorCode;
Prefix = prefix;
IsPriorityEditable = true;
}
/// <summary>
/// The filename of the error. It corresponds to the File column on the Visual Studio Error List window.
/// </summary>
public string Document { get; set; }
/// <summary>
/// The severity of the error
/// </summary>
public ErrorSeverity Severity { get; private set; }
public int ErrorCode { get; private set; }
/// <summary>
/// Line Number of the error
/// </summary>
public int Line { get; set; }
/// <summary>
/// Column Number of the error
/// </summary>
public int Column { get; set; }
/// <summary>
/// Prefix of the error
/// </summary>
public string Prefix { get; private set; }
/// <summary>
/// If the error has any special help topic, this property may hold the ID to the same.
/// </summary>
public string HelpKeyword { get; set; }
/// <summary>
/// Exception associated with the error, or null
/// </summary>
public Exception Exception { get; set; }
/// <summary>
/// Message
/// </summary>
public string Message { get; set; }
/// <summary>
/// Should this message honor the "treat warnings as error" flag?
/// </summary>
public Boolean IsPriorityEditable { get; set; }
/// <summary>
/// Represents the error code used in MSBuild output. This is the prefix and the
/// error code
/// </summary>
/// <returns></returns>
public string BuildErrorCode
{
get { return FormatErrorCode(Prefix, ErrorCode); }
}
public Boolean IsBuildErrorCodeDefined
{
get { return (ErrorCode != UndefinedErrorCode); }
}
/// <summary>
/// true if this error is being displayed in ErrorList. More of an Accounting Mechanism to be used internally.
/// </summary>
public bool IsOnDisplay { get; set; }
public static string FormatErrorCode(string prefix, int code)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}{1:d5}",
prefix,
code);
}
/// <summary>
/// String form of this error.
/// NB: This is for debugging only.
/// </summary>
/// <returns>String form of the error.</returns>
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "{0} - {1}({2},{3}): {4}", FormatErrorCode(Prefix, ErrorCode), Document, Line, Column, Message);
}
}
}

View File

@@ -0,0 +1,71 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Wraps <see cref="IDbCommand"/> objects that could be a <see cref="SqlCommand"/> or
/// a <see cref="ReliableSqlConnection.ReliableSqlCommand"/>, providing common methods across both.
/// </summary>
public sealed class DbCommandWrapper
{
private readonly IDbCommand _command;
private readonly bool _isReliableCommand;
public DbCommandWrapper(IDbCommand command)
{
Validate.IsNotNull(nameof(command), command);
if (command is ReliableSqlConnection.ReliableSqlCommand)
{
_isReliableCommand = true;
}
else if (!(command is SqlCommand))
{
throw new InvalidOperationException(Resources.InvalidCommandType);
}
_command = command;
}
public static bool IsSupportedCommand(IDbCommand command)
{
return command is ReliableSqlConnection.ReliableSqlCommand
|| command is SqlCommand;
}
public event StatementCompletedEventHandler StatementCompleted
{
add
{
SqlCommand sqlCommand = GetAsSqlCommand();
sqlCommand.StatementCompleted += value;
}
remove
{
SqlCommand sqlCommand = GetAsSqlCommand();
sqlCommand.StatementCompleted -= value;
}
}
/// <summary>
/// Gets this as a SqlCommand by casting (if we know it is actually a SqlCommand)
/// or by getting the underlying command (if it's a ReliableSqlCommand)
/// </summary>
private SqlCommand GetAsSqlCommand()
{
if (_isReliableCommand)
{
return ((ReliableSqlConnection.ReliableSqlCommand) _command).GetUnderlyingCommand();
}
return (SqlCommand) _command;
}
}
}

View File

@@ -0,0 +1,113 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Wraps <see cref="IDbConnection"/> objects that could be a <see cref="SqlConnection"/> or
/// a <see cref="ReliableSqlConnection"/>, providing common methods across both.
/// </summary>
internal sealed class DbConnectionWrapper
{
private readonly IDbConnection _connection;
private readonly bool _isReliableConnection;
public DbConnectionWrapper(IDbConnection connection)
{
Validate.IsNotNull(nameof(connection), connection);
if (connection is ReliableSqlConnection)
{
_isReliableConnection = true;
}
else if (!(connection is SqlConnection))
{
throw new InvalidOperationException(Resources.InvalidConnectionType);
}
_connection = connection;
}
public static bool IsSupportedConnection(IDbConnection connection)
{
return connection is ReliableSqlConnection
|| connection is SqlConnection;
}
public event SqlInfoMessageEventHandler InfoMessage
{
add
{
SqlConnection conn = GetAsSqlConnection();
conn.InfoMessage += value;
}
remove
{
SqlConnection conn = GetAsSqlConnection();
conn.InfoMessage -= value;
}
}
public string DataSource
{
get
{
if (_isReliableConnection)
{
return ((ReliableSqlConnection) _connection).DataSource;
}
return ((SqlConnection)_connection).DataSource;
}
}
public string ServerVersion
{
get
{
if (_isReliableConnection)
{
return ((ReliableSqlConnection)_connection).ServerVersion;
}
return ((SqlConnection)_connection).ServerVersion;
}
}
/// <summary>
/// Gets this as a SqlConnection by casting (if we know it is actually a SqlConnection)
/// or by getting the underlying connection (if it's a ReliableSqlConnection)
/// </summary>
public SqlConnection GetAsSqlConnection()
{
if (_isReliableConnection)
{
return ((ReliableSqlConnection) _connection).GetUnderlyingConnection();
}
return (SqlConnection) _connection;
}
/*
TODO - IClonable does not exist in .NET Core.
/// <summary>
/// Clones the connection and ensures it's opened.
/// If it's a SqlConnection it will clone it,
/// and for ReliableSqlConnection it will clone the underling connection.
/// The reason the entire ReliableSqlConnection is not cloned is that it includes
/// several callbacks and we don't want to try and handle deciding how to clone these
/// yet.
/// </summary>
public SqlConnection CloneAndOpenConnection()
{
SqlConnection conn = GetAsSqlConnection();
SqlConnection clonedConn = ((ICloneable) conn).Clone() as SqlConnection;
clonedConn.Open();
return clonedConn;
}
*/
}
}

View File

@@ -0,0 +1,15 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public enum ErrorSeverity
{
Unknown = 0,
Error,
Warning,
Message
}
}

View File

@@ -0,0 +1,19 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// This interface controls the lifetime of settings created as part of the
/// top-of-stack API. Changes made to this context's AmbientData instance will
/// flow to lower in the stack while this object is not disposed.
/// </summary>
public interface IStackSettingsContext : IDisposable
{
AmbientSettings.AmbientData Settings { get; }
}
}

View File

@@ -0,0 +1,247 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// This code is copied from the source described in the comment below.
// =======================================================================================
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
//
// This sample is supplemental to the technical guidance published on the community
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
// sqlmain ./sql/manageability/mfx/common/
//
// =======================================================================================
// Copyright © 2012 Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
// =======================================================================================
// namespace Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling.SqlAzure
// namespace Microsoft.SqlServer.Management.Common
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Diagnostics.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Provides a reliable way of opening connections to and executing commands
/// taking into account potential network unreliability and a requirement for connection retry.
/// </summary>
public sealed partial class ReliableSqlConnection
{
public class ReliableSqlCommand : DbCommand
{
private const int Dummy = 0;
private readonly SqlCommand _command;
// connection is settable
private ReliableSqlConnection _connection;
public ReliableSqlCommand()
: this(null, Dummy)
{
}
public ReliableSqlCommand(ReliableSqlConnection connection)
: this(connection, Dummy)
{
Contract.Requires(connection != null);
}
private ReliableSqlCommand(ReliableSqlConnection connection, int dummy)
{
if (connection != null)
{
_connection = connection;
_command = connection.CreateSqlCommand();
}
else
{
_command = new SqlCommand();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_command.Dispose();
}
}
/// <summary>
/// Gets or sets the text command to run against the data source.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
public override string CommandText
{
get { return _command.CommandText; }
set { _command.CommandText = value; }
}
/// <summary>
/// Gets or sets the wait time before terminating the attempt to execute a command and generating an error.
/// </summary>
public override int CommandTimeout
{
get { return _command.CommandTimeout; }
set { _command.CommandTimeout = value; }
}
/// <summary>
/// Gets or sets a value that specifies how the <see cref="System.Data.Common.DbCommand.CommandText"/> property is interpreted.
/// </summary>
public override CommandType CommandType
{
get { return _command.CommandType; }
set { _command.CommandType = value; }
}
/// <summary>
/// Gets or sets the <see cref="System.Data.Common.DbConnection"/> used by this <see cref="System.Data.Common.DbCommand"/>.
/// </summary>
protected override DbConnection DbConnection
{
get
{
return _connection;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
ReliableSqlConnection newConnection = value as ReliableSqlConnection;
if (newConnection == null)
{
throw new InvalidOperationException(Resources.OnlyReliableConnectionSupported);
}
_connection = newConnection;
_command.Connection = _connection._underlyingConnection;
}
}
/// <summary>
/// Gets the <see cref="System.Data.IDataParameterCollection"/>.
/// </summary>
protected override DbParameterCollection DbParameterCollection
{
get { return _command.Parameters; }
}
/// <summary>
/// Gets or sets the transaction within which the Command object of a .NET Framework data provider executes.
/// </summary>
protected override DbTransaction DbTransaction
{
get { return _command.Transaction; }
set { _command.Transaction = value as SqlTransaction; }
}
/// <summary>
/// Gets or sets a value indicating whether the command object should be visible in a customized interface control.
/// </summary>
public override bool DesignTimeVisible
{
get { return _command.DesignTimeVisible; }
set { _command.DesignTimeVisible = value; }
}
/// <summary>
/// Gets or sets how command results are applied to the System.Data.DataRow when
/// used by the System.Data.IDataAdapter.Update(System.Data.DataSet) method of
/// a <see cref="System.Data.Common.DbDataAdapter"/>.
/// </summary>
public override UpdateRowSource UpdatedRowSource
{
get { return _command.UpdatedRowSource; }
set { _command.UpdatedRowSource = value; }
}
/// <summary>
/// Attempts to cancels the execution of an <see cref="System.Data.IDbCommand"/>.
/// </summary>
public override void Cancel()
{
_command.Cancel();
}
/// <summary>
/// Creates a new instance of an <see cref="System.Data.IDbDataParameter"/> object.
/// </summary>
/// <returns>An <see cref="IDbDataParameter"/> object.</returns>
protected override DbParameter CreateDbParameter()
{
return _command.CreateParameter();
}
/// <summary>
/// Executes an SQL statement against the Connection object of a .NET Framework
/// data provider, and returns the number of rows affected.
/// </summary>
/// <returns>The number of rows affected.</returns>
public override int ExecuteNonQuery()
{
ValidateConnectionIsSet();
return _connection.ExecuteNonQuery(_command);
}
/// <summary>
/// Executes the <see cref="System.Data.IDbCommand.CommandText"/> against the <see cref="System.Data.IDbCommand.Connection"/>
/// and builds an <see cref="System.Data.IDataReader"/> using one of the <see cref="System.Data.CommandBehavior"/> values.
/// </summary>
/// <param name="behavior">One of the <see cref="System.Data.CommandBehavior"/> values.</param>
/// <returns>An <see cref="System.Data.IDataReader"/> object.</returns>
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
ValidateConnectionIsSet();
return (DbDataReader)_connection.ExecuteReader(_command, behavior);
}
/// <summary>
/// Executes the query, and returns the first column of the first row in the
/// resultset returned by the query. Extra columns or rows are ignored.
/// </summary>
/// <returns>The first column of the first row in the resultset.</returns>
public override object ExecuteScalar()
{
ValidateConnectionIsSet();
return _connection.ExecuteScalar(_command);
}
/// <summary>
/// Creates a prepared (or compiled) version of the command on the data source.
/// </summary>
public override void Prepare()
{
_command.Prepare();
}
public SqlCommand GetUnderlyingCommand()
{
return _command;
}
public void ValidateConnectionIsSet()
{
if (_connection == null)
{
throw new InvalidOperationException(Resources.ConnectionPropertyNotSet);
}
}
}
}
}

View File

@@ -0,0 +1,618 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// This code is copied from the source described in the comment below.
// =======================================================================================
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
//
// This sample is supplemental to the technical guidance published on the community
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
// sqlmain ./sql/manageability/mfx/common/
//
// =======================================================================================
// Copyright © 2012 Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
// =======================================================================================
// namespace Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling.SqlAzure
// namespace Microsoft.SqlServer.Management.Common
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Provides a reliable way of opening connections to and executing commands
/// taking into account potential network unreliability and a requirement for connection retry.
/// </summary>
public sealed partial class ReliableSqlConnection : DbConnection, IDisposable
{
private const string QueryAzureSessionId = "SELECT CONVERT(NVARCHAR(36), CONTEXT_INFO())";
private readonly SqlConnection _underlyingConnection;
private readonly RetryPolicy _connectionRetryPolicy;
private RetryPolicy _commandRetryPolicy;
private Guid _azureSessionId;
private bool _isSqlDwDatabase;
/// <summary>
/// Initializes a new instance of the ReliableSqlConnection class with a given connection string
/// and a policy defining whether to retry a request if the connection fails to be opened or a command
/// fails to be successfully executed.
/// </summary>
/// <param name="connectionString">The connection string used to open the SQL Azure database.</param>
/// <param name="connectionRetryPolicy">The retry policy defining whether to retry a request if a connection fails to be established.</param>
/// <param name="commandRetryPolicy">The retry policy defining whether to retry a request if a command fails to be executed.</param>
public ReliableSqlConnection(string connectionString, RetryPolicy connectionRetryPolicy, RetryPolicy commandRetryPolicy, string azureAccountToken)
{
_underlyingConnection = new SqlConnection(connectionString);
_connectionRetryPolicy = connectionRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy();
_commandRetryPolicy = commandRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy();
_underlyingConnection.StateChange += OnConnectionStateChange;
_connectionRetryPolicy.RetryOccurred += RetryConnectionCallback;
_commandRetryPolicy.RetryOccurred += RetryCommandCallback;
if (azureAccountToken != null)
{
_underlyingConnection.AccessToken = azureAccountToken;
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting managed and unmanaged resources.
/// </summary>
/// <param name="disposing">A flag indicating that managed resources must be released.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_connectionRetryPolicy != null)
{
_connectionRetryPolicy.RetryOccurred -= RetryConnectionCallback;
}
if (_commandRetryPolicy != null)
{
_commandRetryPolicy.RetryOccurred -= RetryCommandCallback;
}
_underlyingConnection.StateChange -= OnConnectionStateChange;
if (_underlyingConnection.State == ConnectionState.Open)
{
_underlyingConnection.Close();
}
_underlyingConnection.Dispose();
}
}
/// <summary>
/// Determines if a connection is being made to a SQL DW database.
/// </summary>
/// <param name="conn">A connection object.</param>
private bool IsSqlDwConnection(IDbConnection conn)
{
//Set the connection only if it has not been set earlier.
//This is assuming that it is highly unlikely for a connection to change between instances.
//Hence any subsequent calls to this method will just return the cached value and not
//verify again if this is a SQL DW database connection or not.
if (!CachedServerInfo.Instance.TryGetIsSqlDw(conn, out _isSqlDwDatabase))
{
_isSqlDwDatabase = ReliableConnectionHelper.IsSqlDwDatabase(conn);
CachedServerInfo.Instance.AddOrUpdateIsSqlDw(conn, _isSqlDwDatabase);;
}
return _isSqlDwDatabase;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
internal static void SetLockAndCommandTimeout(IDbConnection conn)
{
Validate.IsNotNull(nameof(conn), conn);
// Make sure we use the underlying connection as ReliableConnection.Open also calls
// this method
ReliableSqlConnection reliableConn = conn as ReliableSqlConnection;
if (reliableConn != null)
{
conn = reliableConn._underlyingConnection;
}
const string setLockTimeout = @"set LOCK_TIMEOUT {0}";
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = string.Format(CultureInfo.InvariantCulture, setLockTimeout, AmbientSettings.LockTimeoutMilliSeconds);
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = CachedServerInfo.Instance.GetQueryTimeoutSeconds(conn);
cmd.ExecuteNonQuery();
}
}
internal static void SetDefaultAnsiSettings(IDbConnection conn, bool isSqlDw)
{
Validate.IsNotNull(nameof(conn), conn);
// Make sure we use the underlying connection as ReliableConnection.Open also calls
// this method
ReliableSqlConnection reliableConn = conn as ReliableSqlConnection;
if (reliableConn != null)
{
conn = reliableConn._underlyingConnection;
}
// Configure the connection with proper ANSI settings and lock timeout
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandTimeout = CachedServerInfo.Instance.GetQueryTimeoutSeconds(conn);
if (!isSqlDw)
{
cmd.CommandText = @"SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;";
}
else
{
cmd.CommandText = @"SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET ARITHABORT ON; SET CONCAT_NULL_YIELDS_NULL ON; SET QUOTED_IDENTIFIER ON;"; //SQL DW does not support NUMERIC_ROUNDABORT
}
cmd.ExecuteNonQuery();
}
}
/// <summary>
/// Gets or sets the connection string for opening a connection to the SQL Azure database.
/// </summary>
public override string ConnectionString
{
get { return _underlyingConnection.ConnectionString; }
set { _underlyingConnection.ConnectionString = value; }
}
/// <summary>
/// Gets the policy which decides whether to retry a connection request, based on how many
/// times the request has been made and the reason for the last failure.
/// </summary>
public RetryPolicy ConnectionRetryPolicy
{
get { return _connectionRetryPolicy; }
}
/// <summary>
/// Gets the policy which decides whether to retry a command, based on how many
/// times the request has been made and the reason for the last failure.
/// </summary>
public RetryPolicy CommandRetryPolicy
{
get { return _commandRetryPolicy; }
set
{
Validate.IsNotNull(nameof(value), value);
if (_commandRetryPolicy != null)
{
_commandRetryPolicy.RetryOccurred -= RetryCommandCallback;
}
_commandRetryPolicy = value;
_commandRetryPolicy.RetryOccurred += RetryCommandCallback;
}
}
/// <summary>
/// Gets the server name from the underlying connection.
/// </summary>
public override string DataSource
{
get { return _underlyingConnection.DataSource; }
}
/// <summary>
/// Gets the server version from the underlying connection.
/// </summary>
public override string ServerVersion
{
get { return _underlyingConnection.ServerVersion; }
}
/// <summary>
/// If the underlying SqlConnection absolutely has to be accessed, for instance
/// to pass to external APIs that require this type of connection, then this
/// can be used.
/// </summary>
/// <returns><see cref="SqlConnection"/></returns>
public SqlConnection GetUnderlyingConnection()
{
return _underlyingConnection;
}
/// <summary>
/// Begins a database transaction with the specified System.Data.IsolationLevel value.
/// </summary>
/// <param name="level">One of the System.Data.IsolationLevel values.</param>
/// <returns>An object representing the new transaction.</returns>
protected override DbTransaction BeginDbTransaction(IsolationLevel level)
{
return _underlyingConnection.BeginTransaction(level);
}
/// <summary>
/// Changes the current database for an open Connection object.
/// </summary>
/// <param name="databaseName">The name of the database to use in place of the current database.</param>
public override void ChangeDatabase(string databaseName)
{
_underlyingConnection.ChangeDatabase(databaseName);
}
/// <summary>
/// Opens a database connection with the settings specified by the ConnectionString
/// property of the provider-specific Connection object.
/// </summary>
public override void Open()
{
// Check if retry policy was specified, if not, disable retries by executing the Open method using RetryPolicy.NoRetry.
_connectionRetryPolicy.ExecuteAction(() =>
{
if (_underlyingConnection.State != ConnectionState.Open)
{
_underlyingConnection.Open();
}
SetLockAndCommandTimeout(_underlyingConnection);
SetDefaultAnsiSettings(_underlyingConnection, IsSqlDwConnection(_underlyingConnection));
});
}
/// <summary>
/// Opens a database connection with the settings specified by the ConnectionString
/// property of the provider-specific Connection object.
/// </summary>
public override Task OpenAsync(CancellationToken token)
{
// Make sure that the token isn't cancelled before we try
if (token.IsCancellationRequested)
{
return Task.FromCanceled(token);
}
// Check if retry policy was specified, if not, disable retries by executing the Open method using RetryPolicy.NoRetry.
try
{
return _connectionRetryPolicy.ExecuteAction(async () =>
{
if (_underlyingConnection.State != ConnectionState.Open)
{
await _underlyingConnection.OpenAsync(token);
}
SetLockAndCommandTimeout(_underlyingConnection);
SetDefaultAnsiSettings(_underlyingConnection, IsSqlDwConnection(_underlyingConnection));
});
}
catch (Exception e)
{
return Task.FromException(e);
}
}
/// <summary>
/// Closes the connection to the database.
/// </summary>
public override void Close()
{
_underlyingConnection.Close();
}
/// <summary>
/// Gets the time to wait while trying to establish a connection before terminating
/// the attempt and generating an error.
/// </summary>
public override int ConnectionTimeout
{
get { return _underlyingConnection.ConnectionTimeout; }
}
/// <summary>
/// Creates and returns an object implementing the IDbCommand interface which is associated
/// with the underlying SqlConnection.
/// </summary>
/// <returns>A <see cref="IDbCommand"/> object.</returns>
protected override DbCommand CreateDbCommand()
{
return CreateReliableCommand();
}
/// <summary>
/// Creates and returns an object implementing the IDbCommand interface which is associated
/// with the underlying SqlConnection.
/// </summary>
/// <returns>A <see cref="SqlCommand"/> object.</returns>
public SqlCommand CreateSqlCommand()
{
return _underlyingConnection.CreateCommand();
}
/// <summary>
/// Gets the name of the current database or the database to be used after a
/// connection is opened.
/// </summary>
public override string Database
{
get { return _underlyingConnection.Database; }
}
/// <summary>
/// Gets the current state of the connection.
/// </summary>
public override ConnectionState State
{
get { return _underlyingConnection.State; }
}
/// <summary>
/// Adds an info message event Listener.
/// </summary>
/// <param name="handler">An info message event Listener.</param>
public void AddInfoMessageHandler(SqlInfoMessageEventHandler handler)
{
_underlyingConnection.InfoMessage += handler;
}
/// <summary>
/// Removes an info message event Listener.
/// </summary>
/// <param name="handler">An info message event Listener.</param>
public void RemoveInfoMessageHandler(SqlInfoMessageEventHandler handler)
{
_underlyingConnection.InfoMessage -= handler;
}
/// <summary>
/// Clears underlying connection pool.
/// </summary>
public void ClearPool()
{
if (_underlyingConnection != null)
{
SqlConnection.ClearPool(_underlyingConnection);
}
}
private void RetryCommandCallback(RetryState retryState)
{
RetryPolicyUtils.RaiseSchemaAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry, _azureSessionId);
}
private void RetryConnectionCallback(RetryState retryState)
{
RetryPolicyUtils.RaiseSchemaAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.ConnectionRetry, _azureSessionId);
}
public void OnConnectionStateChange(object sender, StateChangeEventArgs e)
{
SqlConnection conn = (SqlConnection)sender;
switch (e.CurrentState)
{
case ConnectionState.Open:
RetrieveSessionId();
break;
case ConnectionState.Broken:
case ConnectionState.Closed:
_azureSessionId = Guid.Empty;
break;
case ConnectionState.Connecting:
case ConnectionState.Executing:
case ConnectionState.Fetching:
default:
break;
}
}
private void RetrieveSessionId()
{
try
{
using (IDbCommand command = CreateReliableCommand())
{
IDbConnection connection = command.Connection;
if (!IsSqlDwConnection(connection))
{
command.CommandText = QueryAzureSessionId;
object result = command.ExecuteScalar();
// Only returns a session id for SQL Azure
if (DBNull.Value != result)
{
string sessionId = (string)command.ExecuteScalar();
if (!Guid.TryParse(sessionId, out _azureSessionId))
{
Logger.Write(TraceEventType.Error, Resources.UnableToRetrieveAzureSessionId);
}
}
}
}
}
catch (Exception exception)
{
Logger.Write(TraceEventType.Error, Resources.UnableToRetrieveAzureSessionId + exception.ToString());
}
}
/// <summary>
/// Creates and returns a ReliableSqlCommand object associated
/// with the underlying SqlConnection.
/// </summary>
/// <returns>A <see cref="ReliableSqlCommand"/> object.</returns>
private ReliableSqlCommand CreateReliableCommand()
{
return new ReliableSqlCommand(this);
}
private void VerifyConnectionOpen(IDbCommand command)
{
// Verify whether or not the connection is valid and is open. This code may be retried therefore
// it is important to ensure that a connection is re-established should it have previously failed.
if (command.Connection == null)
{
command.Connection = this;
}
if (command.Connection.State != ConnectionState.Open)
{
SqlConnection.ClearPool(_underlyingConnection);
command.Connection.Open();
}
}
private IDataReader ExecuteReader(IDbCommand command, CommandBehavior behavior)
{
Tuple<string, bool>[] sessionSettings = null;
return _commandRetryPolicy.ExecuteAction<IDataReader>(() =>
{
VerifyConnectionOpen(command);
sessionSettings = CacheOrReplaySessionSettings(command, sessionSettings);
return command.ExecuteReader(behavior);
});
}
// Because retry loses session settings, cache session settings or reply if the settings are already cached.
public Tuple<string, bool>[] CacheOrReplaySessionSettings(IDbCommand originalCommand, Tuple<string, bool>[] sessionSettings)
{
if (sessionSettings == null)
{
sessionSettings = QuerySessionSettings(originalCommand);
}
else
{
SetSessionSettings(originalCommand.Connection, sessionSettings);
}
return sessionSettings;
}
private object ExecuteScalar(IDbCommand command)
{
Tuple<string,bool>[] sessionSettings = null;
return _commandRetryPolicy.ExecuteAction(() =>
{
VerifyConnectionOpen(command);
sessionSettings = CacheOrReplaySessionSettings(command, sessionSettings);
return command.ExecuteScalar();
});
}
private Tuple<string, bool>[] QuerySessionSettings(IDbCommand originalCommand)
{
Tuple<string,bool>[] sessionSettings = new Tuple<string,bool>[2];
IDbConnection connection = originalCommand.Connection;
if (IsSqlDwConnection(connection))
{
// SESSIONPROPERTY is not supported. Use default values for now
sessionSettings[0] = Tuple.Create("ANSI_NULLS", true);
sessionSettings[1] = Tuple.Create("QUOTED_IDENTIFIER", true);
}
else
{
using (IDbCommand localCommand = connection.CreateCommand())
{
// Executing a reader requires preservation of any pending transaction created by the calling command
localCommand.Transaction = originalCommand.Transaction;
localCommand.CommandText = "SELECT ISNULL(SESSIONPROPERTY ('ANSI_NULLS'), 0), ISNULL(SESSIONPROPERTY ('QUOTED_IDENTIFIER'), 1)";
using (IDataReader reader = localCommand.ExecuteReader())
{
if (reader.Read())
{
sessionSettings[0] = Tuple.Create("ANSI_NULLS", ((int)reader[0] == 1));
sessionSettings[1] = Tuple.Create("QUOTED_IDENTIFIER", ((int)reader[1] ==1));
}
else
{
Debug.Assert(false, "Reader cannot be empty");
}
}
}
}
return sessionSettings;
}
private void SetSessionSettings(IDbConnection connection, params Tuple<string, bool>[] settings)
{
List<string> setONOptions = new List<string>();
List<string> setOFFOptions = new List<string>();
if(settings != null)
{
foreach (Tuple<string, bool> setting in settings)
{
if (setting.Item2)
{
setONOptions.Add(setting.Item1);
}
else
{
setOFFOptions.Add(setting.Item1);
}
}
}
SetSessionSettings(connection, setONOptions, "ON");
SetSessionSettings(connection, setOFFOptions, "OFF");
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
private static void SetSessionSettings(IDbConnection connection, List<string> sessionOptions, string onOff)
{
if (sessionOptions.Count > 0)
{
using (IDbCommand localCommand = connection.CreateCommand())
{
StringBuilder builder = new StringBuilder("SET ");
for (int i = 0; i < sessionOptions.Count; i++)
{
if (i > 0)
{
builder.Append(',');
}
builder.Append(sessionOptions[i]);
}
builder.Append(" ");
builder.Append(onOff);
localCommand.CommandText = builder.ToString();
localCommand.ExecuteNonQuery();
}
}
}
private int ExecuteNonQuery(IDbCommand command)
{
Tuple<string, bool>[] sessionSettings = null;
return _commandRetryPolicy.ExecuteAction<int>(() =>
{
VerifyConnectionOpen(command);
sessionSettings = CacheOrReplaySessionSettings(command, sessionSettings);
return command.ExecuteNonQuery();
});
}
}
}

View File

@@ -0,0 +1,157 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Contains string resources used throughout ReliableConnection code.
/// </summary>
public static class Resources
{
internal static string AmbientSettingFormat
{
get
{
return "{0}: {1}";
}
}
public static string ConnectionPassedToIsCloudShouldBeOpen
{
get
{
return "connection passed to IsCloud should be open.";
}
}
public static string ConnectionPropertyNotSet
{
get
{
return "Connection property has not been initialized.";
}
}
public static string ExceptionCannotBeRetried
{
get
{
return "Exception cannot be retried because of err #{0}:{1}";
}
}
public static string ErrorParsingConnectionString
{
get
{
return "Error parsing connection string {0}";
}
}
public static string FailedToCacheIsCloud
{
get
{
return "failed to cache the server property of IsAzure";
}
}
public static string FailedToParseConnectionString
{
get
{
return "failed to parse the provided connection string: {0}";
}
}
public static string IgnoreOnException
{
get
{
return "Retry number {0}. Ignoring Exception: {1}";
}
}
public static string InvalidCommandType
{
get
{
return "Unsupported command object. Use SqlCommand or ReliableSqlCommand.";
}
}
public static string InvalidConnectionType
{
get
{
return "Unsupported connection object. Use SqlConnection or ReliableSqlConnection.";
}
}
internal static string LoggingAmbientSettings
{
get
{
return "Logging Ambient Settings...";
}
}
internal static string Mode
{
get
{
return "Mode";
}
}
public static string OnlyReliableConnectionSupported
{
get
{
return "Connection property can only be set to a value of type ReliableSqlConnection.";
}
}
public static string RetryOnException
{
get
{
return "Retry number {0}. Delaying {1} ms before next retry. Exception: {2}";
}
}
internal static string ThrottlingTypeInfo
{
get
{
return "ThrottlingTypeInfo";
}
}
public static string UnableToAssignValue
{
get
{
return "Unable to assign the value of type {0} to {1}";
}
}
public static string UnableToRetrieveAzureSessionId
{
get
{
return "Unable to retrieve Azure session-id.";
}
}
internal static string ServerInfoCacheMiss
{
get
{
return "Server Info does not have the requested property in the cache";
}
}
}
}

View File

@@ -0,0 +1,45 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// This code is copied from the source described in the comment below.
// =======================================================================================
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
//
// This sample is supplemental to the technical guidance published on the community
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
// sqlmain ./sql/manageability/mfx/common/
//
// =======================================================================================
// Copyright © 2012 Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
// =======================================================================================
// namespace Microsoft.SQL.CAT.BestPractices.SqlAzure.Framework
// namespace Microsoft.SqlServer.Management.Common
using System;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// The special type of exception that provides managed exit from a retry loop. The user code can use this
/// exception to notify the retry policy that no further retry attempts are required.
/// </summary>
[Serializable]
public sealed class RetryLimitExceededException : Exception
{
public RetryLimitExceededException() : base()
{
}
public RetryLimitExceededException(string m, Exception e) : base(m, e)
{
}
}
}

View File

@@ -0,0 +1,44 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Data.SqlClient;
using System.Diagnostics;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public abstract partial class RetryPolicy
{
/// <summary>
/// Provides the error detection logic for temporary faults that are commonly found during data transfer.
/// </summary>
public class DataTransferErrorDetectionStrategy : ErrorDetectionStrategyBase
{
private static readonly DataTransferErrorDetectionStrategy instance = new DataTransferErrorDetectionStrategy();
public static DataTransferErrorDetectionStrategy Instance
{
get { return instance; }
}
protected override bool CanRetrySqlException(SqlException sqlException)
{
// Enumerate through all errors found in the exception.
foreach (SqlError err in sqlException.Errors)
{
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
if (RetryPolicyUtils.IsNonRetryableDataTransferError(err.Number))
{
Logger.Write(TraceEventType.Error, string.Format(Resources.ExceptionCannotBeRetried, err.Number, err.Message));
return false;
}
}
// Default is to treat all SqlException as retriable.
return true;
}
}
}
}

View File

@@ -0,0 +1,97 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public abstract partial class RetryPolicy
{
public interface IErrorDetectionStrategy
{
/// <summary>
/// Determines whether the specified exception represents a temporary failure that can be compensated by a retry.
/// </summary>
/// <param name="ex">The exception object to be verified.</param>
/// <returns>True if the specified exception is considered as temporary, otherwise false.</returns>
bool CanRetry(Exception ex);
/// <summary>
/// Determines whether the specified exception can be ignored.
/// </summary>
/// <param name="ex">The exception object to be verified.</param>
/// <returns>True if the specified exception is considered as non-harmful.</returns>
bool ShouldIgnoreError(Exception ex);
}
/// <summary>
/// Base class with common retry logic. The core behavior for retrying non SqlExceptions is the same
/// across retry policies
/// </summary>
public abstract class ErrorDetectionStrategyBase : IErrorDetectionStrategy
{
public bool CanRetry(Exception ex)
{
if (ex != null)
{
SqlException sqlException;
if ((sqlException = ex as SqlException) != null)
{
return CanRetrySqlException(sqlException);
}
if (ex is InvalidOperationException)
{
// Operations can throw this exception if the connection is killed before the write starts to the server
// However if there's an inner SqlException it may be a CLR load failure or other non-transient error
if (ex.InnerException != null
&& ex.InnerException is SqlException)
{
return CanRetry(ex.InnerException);
}
return true;
}
if (ex is TimeoutException)
{
return true;
}
}
return false;
}
public bool ShouldIgnoreError(Exception ex)
{
if (ex != null)
{
SqlException sqlException;
if ((sqlException = ex as SqlException) != null)
{
return ShouldIgnoreSqlException(sqlException);
}
if (ex is InvalidOperationException)
{
// Operations can throw this exception if the connection is killed before the write starts to the server
// However if there's an inner SqlException it may be a CLR load failure or other non-transient error
if (ex.InnerException != null
&& ex.InnerException is SqlException)
{
return ShouldIgnoreError(ex.InnerException);
}
}
}
return false;
}
protected virtual bool ShouldIgnoreSqlException(SqlException sqlException)
{
return false;
}
protected abstract bool CanRetrySqlException(SqlException sqlException);
}
}
}

View File

@@ -0,0 +1,43 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public abstract partial class RetryPolicy
{
/// <summary>
/// Provides the error detection logic for temporary faults that are commonly found in SQL Azure.
/// The same errors CAN occur on premise also, but they are not seen as often.
/// </summary>
public sealed class NetworkConnectivityErrorDetectionStrategy : ErrorDetectionStrategyBase
{
private static NetworkConnectivityErrorDetectionStrategy instance = new NetworkConnectivityErrorDetectionStrategy();
public static NetworkConnectivityErrorDetectionStrategy Instance
{
get { return instance; }
}
protected override bool CanRetrySqlException(SqlException sqlException)
{
// Enumerate through all errors found in the exception.
bool foundRetryableError = false;
foreach (SqlError err in sqlException.Errors)
{
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
if (!RetryPolicyUtils.IsRetryableNetworkConnectivityError(err.Number))
{
// If any error is not retryable then cannot retry
return false;
}
foundRetryableError = true;
}
return foundRetryableError;
}
}
}
}

View File

@@ -0,0 +1,63 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public abstract partial class RetryPolicy
{
/// <summary>
/// Provides the error detection logic for temporary faults that are commonly found in SQL Azure.
/// This strategy is similar to SqlAzureTemporaryErrorDetectionStrategy, but it exposes ways
/// to accept a certain exception and treat it as passing.
/// For example, if we are retrying, and we get a failure that an object already exists, we might
/// want to consider this as passing since the first execution that has timed out (or failed for some other temporary error)
/// might have managed to create the object.
/// </summary>
public class SqlAzureTemporaryAndIgnorableErrorDetectionStrategy : ErrorDetectionStrategyBase
{
/// <summary>
/// Azure error that can be ignored
/// </summary>
private readonly IList<int> ignorableAzureErrors = null;
public SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(params int[] ignorableErrors)
{
this.ignorableAzureErrors = ignorableErrors;
}
protected override bool CanRetrySqlException(SqlException sqlException)
{
// Enumerate through all errors found in the exception.
bool foundRetryableError = false;
foreach (SqlError err in sqlException.Errors)
{
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
if (!RetryPolicyUtils.IsRetryableAzureError(err.Number))
{
return false;
}
foundRetryableError = true;
}
return foundRetryableError;
}
protected override bool ShouldIgnoreSqlException(SqlException sqlException)
{
int errorNumber = sqlException.Number;
if (ignorableAzureErrors == null)
{
return false;
}
return ignorableAzureErrors.Contains(errorNumber);
}
}
}
}

View File

@@ -0,0 +1,43 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public abstract partial class RetryPolicy
{
/// <summary>
/// Provides the error detection logic for temporary faults that are commonly found in SQL Azure.
/// The same errors CAN occur on premise also, but they are not seen as often.
/// </summary>
internal sealed class SqlAzureTemporaryErrorDetectionStrategy : ErrorDetectionStrategyBase
{
private static SqlAzureTemporaryErrorDetectionStrategy instance = new SqlAzureTemporaryErrorDetectionStrategy();
public static SqlAzureTemporaryErrorDetectionStrategy Instance
{
get { return instance; }
}
protected override bool CanRetrySqlException(SqlException sqlException)
{
// Enumerate through all errors found in the exception.
bool foundRetryableError = false;
foreach (SqlError err in sqlException.Errors)
{
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
if (!RetryPolicyUtils.IsRetryableAzureError(err.Number))
{
// If any error is not retryable then cannot retry
return false;
}
foundRetryableError = true;
}
return foundRetryableError;
}
}
}
}

View File

@@ -0,0 +1,357 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public abstract partial class RetryPolicy
{
/// <summary>
/// Implements an object holding the decoded reason code returned from SQL Azure when encountering throttling conditions.
/// </summary>
[Serializable]
public class ThrottlingReason
{
/// <summary>
/// Returns the error number that corresponds to throttling conditions reported by SQL Azure.
/// </summary>
public const int ThrottlingErrorNumber = 40501;
/// <summary>
/// Gets an unknown throttling condition in the event the actual throttling condition cannot be determined.
/// </summary>
public static ThrottlingReason Unknown
{
get
{
var unknownCondition = new ThrottlingReason() { ThrottlingMode = ThrottlingMode.Unknown };
unknownCondition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Unknown, ThrottlingType.Unknown));
return unknownCondition;
}
}
/// <summary>
/// Maintains a collection of key-value pairs where a key is resource type and a value is the type of throttling applied to the given resource type.
/// </summary>
private readonly IList<Tuple<ThrottledResourceType, ThrottlingType>> throttledResources = new List<Tuple<ThrottledResourceType, ThrottlingType>>(9);
/// <summary>
/// Provides a compiled regular expression used for extracting the reason code from the error message.
/// </summary>
private static readonly Regex sqlErrorCodeRegEx = new Regex(@"Code:\s*(\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Gets the value that reflects the throttling mode in SQL Azure.
/// </summary>
public ThrottlingMode ThrottlingMode
{
get;
private set;
}
/// <summary>
/// Gets the list of resources in SQL Azure that were subject to throttling conditions.
/// </summary>
public IEnumerable<Tuple<ThrottledResourceType, ThrottlingType>> ThrottledResources
{
get
{
return this.throttledResources;
}
}
/// <summary>
/// Determines throttling conditions from the specified SQL exception.
/// </summary>
/// <param name="ex">The <see cref="SqlException"/> object containing information relevant to an error returned by SQL Server when encountering throttling conditions.</param>
/// <returns>An instance of the object holding the decoded reason codes returned from SQL Azure upon encountering throttling conditions.</returns>
public static ThrottlingReason FromException(SqlException ex)
{
if (ex != null)
{
foreach (SqlError error in ex.Errors)
{
if (error.Number == ThrottlingErrorNumber)
{
return FromError(error);
}
}
}
return Unknown;
}
/// <summary>
/// Determines the throttling conditions from the specified SQL error.
/// </summary>
/// <param name="error">The <see cref="SqlError"/> object containing information relevant to a warning or error returned by SQL Server.</param>
/// <returns>An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions.</returns>
public static ThrottlingReason FromError(SqlError error)
{
if (error != null)
{
var match = sqlErrorCodeRegEx.Match(error.Message);
int reasonCode = 0;
if (match.Success && Int32.TryParse(match.Groups[1].Value, out reasonCode))
{
return FromReasonCode(reasonCode);
}
}
return Unknown;
}
/// <summary>
/// Determines the throttling conditions from the specified reason code.
/// </summary>
/// <param name="reasonCode">The reason code returned by SQL Azure which contains the throttling mode and the exceeded resource types.</param>
/// <returns>An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions.</returns>
public static ThrottlingReason FromReasonCode(int reasonCode)
{
if (reasonCode > 0)
{
// Decode throttling mode from the last 2 bits.
ThrottlingMode throttlingMode = (ThrottlingMode)(reasonCode & 3);
var condition = new ThrottlingReason() { ThrottlingMode = throttlingMode };
// Shift 8 bits to truncate throttling mode.
int groupCode = reasonCode >> 8;
// Determine throttling type for all well-known resources that may be subject to throttling conditions.
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalDatabaseSpace, (ThrottlingType)(groupCode & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalLogSpace, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.LogWriteIODelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DataReadIODelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.CPU, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DatabaseSize, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.WorkerThreads, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
return condition;
}
else
{
return Unknown;
}
}
/// <summary>
/// Gets a value indicating whether physical data file space throttling was reported by SQL Azure.
/// </summary>
public bool IsThrottledOnDataSpace
{
get
{
return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalDatabaseSpace).Count() > 0;
}
}
/// <summary>
/// Gets a value indicating whether physical log space throttling was reported by SQL Azure.
/// </summary>
public bool IsThrottledOnLogSpace
{
get
{
return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalLogSpace).Count() > 0;
}
}
/// <summary>
/// Gets a value indicating whether transaction activity throttling was reported by SQL Azure.
/// </summary>
public bool IsThrottledOnLogWrite
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.LogWriteIODelay).Count() > 0; }
}
/// <summary>
/// Gets a value indicating whether data read activity throttling was reported by SQL Azure.
/// </summary>
public bool IsThrottledOnDataRead
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DataReadIODelay).Count() > 0; }
}
/// <summary>
/// Gets a value indicating whether CPU throttling was reported by SQL Azure.
/// </summary>
public bool IsThrottledOnCPU
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.CPU).Count() > 0; }
}
/// <summary>
/// Gets a value indicating whether database size throttling was reported by SQL Azure.
/// </summary>
public bool IsThrottledOnDatabaseSize
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DatabaseSize).Count() > 0; }
}
/// <summary>
/// Gets a value indicating whether concurrent requests throttling was reported by SQL Azure.
/// </summary>
public bool IsThrottledOnWorkerThreads
{
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.WorkerThreads).Count() > 0; }
}
/// <summary>
/// Gets a value indicating whether throttling conditions were not determined with certainty.
/// </summary>
public bool IsUnknown
{
get { return ThrottlingMode == ThrottlingMode.Unknown; }
}
/// <summary>
/// Returns a textual representation the current ThrottlingReason object including the information held with respect to throttled resources.
/// </summary>
/// <returns>A string that represents the current ThrottlingReason object.</returns>
public override string ToString()
{
StringBuilder result = new StringBuilder();
result.AppendFormat(Resources.Mode, ThrottlingMode);
var resources = this.throttledResources.Where(x => x.Item1 != ThrottledResourceType.Internal).
Select<Tuple<ThrottledResourceType, ThrottlingType>, string>(x => String.Format(CultureInfo.CurrentCulture, Resources.ThrottlingTypeInfo, x.Item1, x.Item2)).
OrderBy(x => x).ToArray();
result.Append(String.Join(", ", resources));
return result.ToString();
}
}
#region ThrottlingMode enumeration
/// <summary>
/// Defines the possible throttling modes in SQL Azure.
/// </summary>
public enum ThrottlingMode
{
/// <summary>
/// Corresponds to "No Throttling" throttling mode whereby all SQL statements can be processed.
/// </summary>
NoThrottling = 0,
/// <summary>
/// Corresponds to "Reject Update / Insert" throttling mode whereby SQL statements such as INSERT, UPDATE, CREATE TABLE and CREATE INDEX are rejected.
/// </summary>
RejectUpdateInsert = 1,
/// <summary>
/// Corresponds to "Reject All Writes" throttling mode whereby SQL statements such as INSERT, UPDATE, DELETE, CREATE, DROP are rejected.
/// </summary>
RejectAllWrites = 2,
/// <summary>
/// Corresponds to "Reject All" throttling mode whereby all SQL statements are rejected.
/// </summary>
RejectAll = 3,
/// <summary>
/// Corresponds to an unknown throttling mode whereby throttling mode cannot be determined with certainty.
/// </summary>
Unknown = -1
}
#endregion
#region ThrottlingType enumeration
/// <summary>
/// Defines the possible throttling types in SQL Azure.
/// </summary>
public enum ThrottlingType
{
/// <summary>
/// Indicates that no throttling was applied to a given resource.
/// </summary>
None = 0,
/// <summary>
/// Corresponds to a Soft throttling type. Soft throttling is applied when machine resources such as, CPU, IO, storage, and worker threads exceed
/// predefined safety thresholds despite the load balancers best efforts.
/// </summary>
Soft = 1,
/// <summary>
/// Corresponds to a Hard throttling type. Hard throttling is applied when the machine is out of resources, for example storage space.
/// With hard throttling, no new connections are allowed to the databases hosted on the machine until resources are freed up.
/// </summary>
Hard = 2,
/// <summary>
/// Corresponds to an unknown throttling type in the event when the throttling type cannot be determined with certainty.
/// </summary>
Unknown = 3
}
#endregion
#region ThrottledResourceType enumeration
/// <summary>
/// Defines the types of resources in SQL Azure which may be subject to throttling conditions.
/// </summary>
public enum ThrottledResourceType
{
/// <summary>
/// Corresponds to "Physical Database Space" resource which may be subject to throttling.
/// </summary>
PhysicalDatabaseSpace = 0,
/// <summary>
/// Corresponds to "Physical Log File Space" resource which may be subject to throttling.
/// </summary>
PhysicalLogSpace = 1,
/// <summary>
/// Corresponds to "Transaction Log Write IO Delay" resource which may be subject to throttling.
/// </summary>
LogWriteIODelay = 2,
/// <summary>
/// Corresponds to "Database Read IO Delay" resource which may be subject to throttling.
/// </summary>
DataReadIODelay = 3,
/// <summary>
/// Corresponds to "CPU" resource which may be subject to throttling.
/// </summary>
CPU = 4,
/// <summary>
/// Corresponds to "Database Size" resource which may be subject to throttling.
/// </summary>
DatabaseSize = 5,
/// <summary>
/// Corresponds to "SQL Worker Thread Pool" resource which may be subject to throttling.
/// </summary>
WorkerThreads = 7,
/// <summary>
/// Corresponds to an internal resource which may be subject to throttling.
/// </summary>
Internal = 6,
/// <summary>
/// Corresponds to an unknown resource type in the event when the actual resource cannot be determined with certainty.
/// </summary>
Unknown = -1
}
#endregion
}
}

View File

@@ -0,0 +1,542 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// This code is copied from the source described in the comment below.
// =======================================================================================
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
//
// This sample is supplemental to the technical guidance published on the community
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
// sqlmain ./sql/manageability/mfx/common/
//
// =======================================================================================
// Copyright © 2012 Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
// =======================================================================================
// namespace Microsoft.SQL.CAT.BestPractices.SqlAzure.Framework
// namespace Microsoft.SqlServer.Management.Common
using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Threading;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Implements a policy defining and implementing the retry mechanism for unreliable actions.
/// </summary>
public abstract partial class RetryPolicy
{
/// <summary>
/// Defines a callback delegate which will be invoked whenever a retry condition is encountered.
/// </summary>
/// <param name="retryState">The state of current retry attempt.</param>
public delegate void RetryCallbackDelegate(RetryState retryState);
/// <summary>
/// Defines a callback delegate which will be invoked whenever an error is ignored on retry.
/// </summary>
/// <param name="retryState">The state of current retry attempt.</param>
public delegate void IgnoreErrorCallbackDelegate(RetryState retryState);
private readonly IErrorDetectionStrategy _errorDetectionStrategy;
protected RetryPolicy(IErrorDetectionStrategy strategy)
{
Contract.Assert(strategy != null);
_errorDetectionStrategy = strategy;
this.FastFirstRetry = true;
//TODO Defect 1078447 Validate whether CommandTimeout needs to be used differently in schema/data scenarios
this.CommandTimeoutInSeconds = AmbientSettings.LongRunningQueryTimeoutSeconds;
}
/// <summary>
/// An instance of a callback delegate which will be invoked whenever a retry condition is encountered.
/// </summary>
public event RetryCallbackDelegate RetryOccurred;
/// <summary>
/// An instance of a callback delegate which will be invoked whenever an error is ignored on retry.
/// </summary>
public event IgnoreErrorCallbackDelegate IgnoreErrorOccurred;
/// <summary>
/// Gets or sets a value indicating whether or not the very first retry attempt will be made immediately
/// whereas the subsequent retries will remain subject to retry interval.
/// </summary>
public bool FastFirstRetry { get; set; }
/// <summary>
/// Gets or sets the timeout in seconds of sql commands
/// </summary>
public int CommandTimeoutInSeconds
{
get;
set;
}
/// <summary>
/// Gets the error detection strategy of this retry policy
/// </summary>
internal IErrorDetectionStrategy ErrorDetectionStrategy
{
get
{
return _errorDetectionStrategy;
}
}
/// <summary>
/// We should only ignore errors if they happen after the first retry.
/// This flag is used to allow the ignore even on first try, for testing purposes.
/// </summary>
/// <remarks>
/// This flag is currently being used for TESTING PURPOSES ONLY.
/// </remarks>
public bool ShouldIgnoreOnFirstTry
{
get;
set;
}
protected static bool IsLessThanMaxRetryCount(int currentRetryCount, int maxRetryCount)
{
return currentRetryCount <= maxRetryCount;
}
/// <summary>
/// Repetitively executes the specified action while it satisfies the current retry policy.
/// </summary>
/// <param name="action">A delegate representing the executable action which doesn't return any results.</param>
/// <param name="token">Cancellation token to cancel action between retries.</param>
public void ExecuteAction(Action action, CancellationToken? token = null)
{
ExecuteAction(
_ => action(), token);
}
/// <summary>
/// Repetitively executes the specified action while it satisfies the current retry policy.
/// </summary>
/// <param name="action">A delegate representing the executable action which doesn't return any results.</param>
/// <param name="token">Cancellation token to cancel action between retries.</param>
public void ExecuteAction(Action<RetryState> action, CancellationToken? token = null)
{
ExecuteAction<object>(
retryState =>
{
action(retryState);
return null;
}, token);
}
/// <summary>
/// Repetitively executes the specified action while it satisfies the current retry policy.
/// </summary>
/// <typeparam name="T">The type of result expected from the executable action.</typeparam>
/// <param name="func">A delegate representing the executable action which returns the result of type T.</param>
/// <param name="token">Cancellation token to cancel action between retries.</param>
/// <returns>The result from the action.</returns>
public T ExecuteAction<T>(Func<T> func, CancellationToken? token = null)
{
return ExecuteAction(
_ => func(), token);
}
/// <summary>
/// Repetitively executes the specified action while it satisfies the current retry policy.
/// </summary>
/// <typeparam name="R">The type of result expected from the executable action.</typeparam>
/// <param name="func">A delegate representing the executable action which returns the result of type R.</param>
/// <param name="token">Cancellation token to cancel action between retries.</param>
/// <returns>The result from the action.</returns>
public R ExecuteAction<R>(Func<RetryState, R> func, CancellationToken? token = null)
{
RetryState retryState = CreateRetryState();
if (token != null)
{
token.Value.ThrowIfCancellationRequested();
}
while (true)
{
try
{
return func(retryState);
}
catch (RetryLimitExceededException limitExceededEx)
{
// The user code can throw a RetryLimitExceededException to force the exit from the retry loop.
// The RetryLimitExceeded exception can have an inner exception attached to it. This is the exception
// which we will have to throw up the stack so that callers can handle it.
if (limitExceededEx.InnerException != null)
{
throw limitExceededEx.InnerException;
}
return default(R);
}
catch (Exception ex)
{
retryState.LastError = ex;
if (retryState.RetryCount > 0 || this.ShouldIgnoreOnFirstTry)
{
// If we can ignore this error, then break out of the loop and consider this execution as passing
// We return the default value for the type R
if (ShouldIgnoreError(retryState))
{
OnIgnoreErrorOccurred(retryState);
return default(R);
}
}
retryState.RetryCount++;
if (!ShouldRetry(retryState))
{
throw;
}
}
OnRetryOccurred(retryState);
if ((retryState.RetryCount > 1 || !FastFirstRetry) && !retryState.IsDelayDisabled)
{
Thread.Sleep(retryState.Delay);
}
// check for cancellation after delay.
if (token != null)
{
token.Value.ThrowIfCancellationRequested();
}
}
}
protected virtual RetryState CreateRetryState()
{
return new RetryState();
}
public bool IsRetryableException(Exception ex)
{
return ErrorDetectionStrategy.CanRetry(ex);
}
public bool ShouldRetry(RetryState retryState)
{
bool canRetry = ErrorDetectionStrategy.CanRetry(retryState.LastError);
bool shouldRetry = canRetry
&& ShouldRetryImpl(retryState);
Logger.Write(TraceEventType.Error,
string.Format(
CultureInfo.InvariantCulture,
"Retry requested: Retry count = {0}. Delay = {1}, SQL Error Number = {2}, Can retry error = {3}, Will retry = {4}",
retryState.RetryCount,
retryState.Delay,
GetErrorNumber(retryState.LastError),
canRetry,
shouldRetry));
// Perform an extra check in the delay interval. Should prevent from accidentally ending up with the value of -1 which will block a thread indefinitely.
// In addition, any other negative numbers will cause an ArgumentOutOfRangeException fault which will be thrown by Thread.Sleep.
if (retryState.Delay.TotalMilliseconds < 0)
{
retryState.Delay = TimeSpan.Zero;
}
return shouldRetry;
}
public bool ShouldIgnoreError(RetryState retryState)
{
bool shouldIgnoreError = ErrorDetectionStrategy.ShouldIgnoreError(retryState.LastError);
Logger.Write(TraceEventType.Error,
string.Format(
CultureInfo.InvariantCulture,
"Ignore Error requested: Retry count = {0}. Delay = {1}, SQL Error Number = {2}, Should Ignore Error = {3}",
retryState.RetryCount,
retryState.Delay,
GetErrorNumber(retryState.LastError),
shouldIgnoreError));
return shouldIgnoreError;
}
/* TODO - Error code does not exist in SqlException for .NET Core
private static int? GetErrorCode(Exception ex)
{
SqlException sqlEx= ex as SqlException;
if (sqlEx == null)
{
return null;
}
return sqlEx.ErrorCode;
}
*/
public static int? GetErrorNumber(Exception ex)
{
SqlException sqlEx = ex as SqlException;
if (sqlEx == null)
{
return null;
}
return sqlEx.Number;
}
protected abstract bool ShouldRetryImpl(RetryState retryState);
/// <summary>
/// Notifies the subscribers whenever a retry condition is encountered.
/// </summary>
/// <param name="retryState">The state of current retry attempt.</param>
protected virtual void OnRetryOccurred(RetryState retryState)
{
var retryOccurred = RetryOccurred;
if (retryOccurred != null)
{
retryOccurred(retryState);
}
}
/// <summary>
/// Notifies the subscribers whenever an error is ignored on retry.
/// </summary>
/// <param name="retryState">The state of current retry attempt.</param>
protected virtual void OnIgnoreErrorOccurred(RetryState retryState)
{
var ignoreErrorOccurred = IgnoreErrorOccurred;
if (ignoreErrorOccurred != null)
{
ignoreErrorOccurred(retryState);
}
}
public class FixedDelayPolicy : RetryPolicy
{
private readonly int _maxRetryCount;
private readonly TimeSpan _intervalBetweenRetries;
/// <summary>
/// Constructs a new instance of the TRetryPolicy class with the specified number of retry attempts and time interval between retries.
/// </summary>
/// <param name="strategy">The <see cref="RetryPolicy.IErrorDetectionStrategy"/> to use when checking whether an error is retryable</param>
/// <param name="maxRetryCount">The max number of retry attempts. Should be 1-indexed.</param>
/// <param name="intervalBetweenRetries">The interval between retries.</param>
public FixedDelayPolicy(IErrorDetectionStrategy strategy, int maxRetryCount, TimeSpan intervalBetweenRetries)
: base(strategy)
{
Contract.Assert(maxRetryCount >= 0, "maxRetryCount cannot be a negative number");
Contract.Assert(intervalBetweenRetries.Ticks >= 0, "intervalBetweenRetries cannot be negative");
_maxRetryCount = maxRetryCount;
_intervalBetweenRetries = intervalBetweenRetries;
}
protected override bool ShouldRetryImpl(RetryState retryState)
{
Contract.Assert(retryState != null);
if (IsLessThanMaxRetryCount(retryState.RetryCount, _maxRetryCount))
{
retryState.Delay = _intervalBetweenRetries;
return true;
}
retryState.Delay = TimeSpan.Zero;
return false;
}
}
public class ProgressiveRetryPolicy : RetryPolicy
{
private readonly int _maxRetryCount;
private readonly TimeSpan _initialInterval;
private readonly TimeSpan _increment;
/// <summary>
/// Constructs a new instance of the TRetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries.
/// </summary>
/// <param name="strategy">The <see cref="RetryPolicy.IErrorDetectionStrategy"/> to use when checking whether an error is retryable</param>
/// <param name="maxRetryCount">The maximum number of retry attempts. Should be 1-indexed.</param>
/// <param name="initialInterval">The initial interval which will apply for the first retry.</param>
/// <param name="increment">The incremental time value which will be used for calculating the progressive delay between retries.</param>
public ProgressiveRetryPolicy(IErrorDetectionStrategy strategy, int maxRetryCount, TimeSpan initialInterval, TimeSpan increment)
: base(strategy)
{
Contract.Assert(maxRetryCount >= 0, "maxRetryCount cannot be a negative number");
Contract.Assert(initialInterval.Ticks >= 0, "retryInterval cannot be negative");
Contract.Assert(increment.Ticks >= 0, "retryInterval cannot be negative");
_maxRetryCount = maxRetryCount;
_initialInterval = initialInterval;
_increment = increment;
}
protected override bool ShouldRetryImpl(RetryState retryState)
{
Contract.Assert(retryState != null);
if (IsLessThanMaxRetryCount(retryState.RetryCount, _maxRetryCount))
{
retryState.Delay = TimeSpan.FromMilliseconds(_initialInterval.TotalMilliseconds + (_increment.TotalMilliseconds * (retryState.RetryCount - 1)));
return true;
}
retryState.Delay = TimeSpan.Zero;
return false;
}
}
internal class ExponentialDelayRetryPolicy : RetryPolicy
{
private readonly int _maxRetryCount;
private readonly double _intervalFactor;
private readonly TimeSpan _minInterval;
private readonly TimeSpan _maxInterval;
/// <summary>
/// Constructs a new instance of the TRetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries.
/// </summary>
/// <param name="strategy">The <see cref="RetryPolicy.IErrorDetectionStrategy"/> to use when checking whether an error is retryable</param>
/// <param name="maxRetryCount">The maximum number of retry attempts.</param>
/// <param name="intervalFactor">Controls the speed at which the delay increases - the retryCount is raised to this power as
/// part of the function </param>
/// <param name="minInterval">Minimum interval between retries. The basis for all backoff calculations</param>
/// <param name="maxInterval">Maximum interval between retries. Backoff will not take longer than this period.</param>
public ExponentialDelayRetryPolicy(IErrorDetectionStrategy strategy, int maxRetryCount, double intervalFactor, TimeSpan minInterval, TimeSpan maxInterval)
: base(strategy)
{
Contract.Assert(maxRetryCount >= 0, "maxRetryCount cannot be a negative number");
Contract.Assert(intervalFactor > 1, "intervalFactor Must be > 1 so that the delay increases exponentially");
Contract.Assert(minInterval.Ticks >= 0, "minInterval cannot be negative");
Contract.Assert(maxInterval.Ticks >= 0, "maxInterval cannot be negative");
Contract.Assert(maxInterval.Ticks >= minInterval.Ticks, "maxInterval must be greater than minInterval");
_maxRetryCount = maxRetryCount;
_intervalFactor = intervalFactor;
_minInterval = minInterval;
_maxInterval = maxInterval;
}
protected override bool ShouldRetryImpl(RetryState retryState)
{
Contract.Assert(retryState != null);
if (IsLessThanMaxRetryCount(retryState.RetryCount, _maxRetryCount))
{
retryState.Delay = RetryPolicyUtils.CalcExponentialRetryDelay(retryState.RetryCount, _intervalFactor, _minInterval, _maxInterval);
return true;
}
retryState.Delay = TimeSpan.Zero;
return false;
}
}
public class TimeBasedRetryPolicy : RetryPolicy
{
private readonly TimeSpan _minTotalRetryTimeLimit;
private readonly TimeSpan _maxTotalRetryTimeLimit;
private readonly double _totalRetryTimeLimitRate;
private readonly TimeSpan _minInterval;
private readonly TimeSpan _maxInterval;
private readonly double _intervalFactor;
private readonly Stopwatch _stopwatch;
public TimeBasedRetryPolicy(
IErrorDetectionStrategy strategy,
TimeSpan minTotalRetryTimeLimit,
TimeSpan maxTotalRetryTimeLimit,
double totalRetryTimeLimitRate,
TimeSpan minInterval,
TimeSpan maxInterval,
double intervalFactor)
: base(strategy)
{
Contract.Assert(minTotalRetryTimeLimit.Ticks >= 0);
Contract.Assert(maxTotalRetryTimeLimit.Ticks >= minTotalRetryTimeLimit.Ticks);
Contract.Assert(totalRetryTimeLimitRate >= 0);
Contract.Assert(minInterval.Ticks >= 0);
Contract.Assert(maxInterval.Ticks >= minInterval.Ticks);
Contract.Assert(intervalFactor >= 1);
_minTotalRetryTimeLimit = minTotalRetryTimeLimit;
_maxTotalRetryTimeLimit = maxTotalRetryTimeLimit;
_totalRetryTimeLimitRate = totalRetryTimeLimitRate;
_minInterval = minInterval;
_maxInterval = maxInterval;
_intervalFactor = intervalFactor;
_stopwatch = Stopwatch.StartNew();
}
protected override bool ShouldRetryImpl(RetryState retryStateObj)
{
Contract.Assert(retryStateObj is RetryStateEx);
RetryStateEx retryState = (RetryStateEx)retryStateObj;
// Calculate the delay as exponential value based on the number of retries.
retryState.Delay =
RetryPolicyUtils.CalcExponentialRetryDelay(
retryState.RetryCount,
_intervalFactor,
_minInterval,
_maxInterval);
// Add the delay to the total retry time
retryState.TotalRetryTime = retryState.TotalRetryTime + retryState.Delay;
// Calculate the maximum total retry time depending on how long ago was the task (this retry policy) started.
// Longer running tasks are less eager to abort since, more work is has been done.
TimeSpan totalRetryTimeLimit = checked(TimeSpan.FromMilliseconds(
Math.Max(
Math.Min(
_stopwatch.ElapsedMilliseconds * _totalRetryTimeLimitRate,
_maxTotalRetryTimeLimit.TotalMilliseconds),
_minTotalRetryTimeLimit.TotalMilliseconds)));
if (retryState.TotalRetryTime <= totalRetryTimeLimit)
{
return true;
}
retryState.Delay = TimeSpan.Zero;
return false;
}
protected override RetryState CreateRetryState()
{
return new RetryStateEx { TotalRetryTime = TimeSpan.Zero };
}
public sealed class RetryStateEx : RetryState
{
public TimeSpan TotalRetryTime { get; set; }
}
}
}
}

View File

@@ -0,0 +1,459 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Diagnostics;
using System.Globalization;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
internal sealed class RetryPolicyDefaults
{
/// <summary>
/// The default number of retry attempts.
/// </summary>
public const int DefaulSchemaRetryCount = 6;
/// <summary>
/// The default number of retry attempts for create database.
/// </summary>
public const int DefaultCreateDatabaseRetryCount = 5;
/// <summary>
/// The default amount of time defining an interval between retries.
/// </summary>
public static readonly TimeSpan DefaultSchemaMinInterval = TimeSpan.FromSeconds(2.75);
/// <summary>
/// The default factor to use when determining exponential backoff between retries.
/// </summary>
public const double DefaultBackoffIntervalFactor = 2.0;
/// <summary>
/// The default maximum time between retries.
/// </summary>
public static readonly TimeSpan DefaultMaxRetryInterval = TimeSpan.FromSeconds(60);
/// <summary>
/// The default number of retry attempts.
/// </summary>
public static readonly int DefaultDataCommandRetryCount = 5;
/// <summary>
/// The default number of retry attempts for a connection related error
/// </summary>
public static readonly int DefaultConnectionRetryCount = 6;
/// <summary>
/// The default amount of time defining an interval between retries.
/// </summary>
public static readonly TimeSpan DefaultDataMinInterval = TimeSpan.FromSeconds(1.0);
/// <summary>
/// The default amount of time defining a time increment between retry attempts in the progressive delay policy.
/// </summary>
public static readonly TimeSpan DefaultProgressiveRetryIncrement = TimeSpan.FromMilliseconds(500);
}
/// <summary>
/// Implements a collection of the RetryPolicyInfo elements holding retry policy settings.
/// </summary>
public sealed class RetryPolicyFactory
{
/// <summary>
/// Returns a default policy that does no retries, it just invokes action exactly once.
/// </summary>
public static readonly RetryPolicy NoRetryPolicy = RetryPolicyFactory.CreateNoRetryPolicy();
/// <summary>
/// Returns a default policy that does no retries, it just invokes action exactly once.
/// </summary>
public static readonly RetryPolicy PrimaryKeyViolationRetryPolicy = RetryPolicyFactory.CreatePrimaryKeyCommandRetryPolicy();
/// <summary>
/// Implements a strategy that ignores any transient errors.
/// Internal for testing purposes only
/// </summary>
public sealed class TransientErrorIgnoreStrategy : RetryPolicy.IErrorDetectionStrategy
{
private static readonly TransientErrorIgnoreStrategy _instance = new TransientErrorIgnoreStrategy();
public static TransientErrorIgnoreStrategy Instance
{
get { return _instance; }
}
public bool CanRetry(Exception ex)
{
return false;
}
public bool ShouldIgnoreError(Exception ex)
{
return false;
}
}
/// <summary>
/// Creates and returns a default Retry Policy for Schema based operations.
/// </summary>
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
public static RetryPolicy CreateDefaultSchemaCommandRetryPolicy(bool useRetry, int retriesPerPhase = RetryPolicyDefaults.DefaulSchemaRetryCount)
{
RetryPolicy policy;
if (useRetry)
{
policy = new RetryPolicy.ExponentialDelayRetryPolicy(
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
retriesPerPhase,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultSchemaMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
policy.FastFirstRetry = false;
}
else
{
policy = CreateNoRetryPolicy();
}
return policy;
}
/// <summary>
/// Creates and returns a default Retry Policy for Schema based connection operations.
/// </summary>
/// <remarks>The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a connection retry. </remarks>
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
public static RetryPolicy CreateSchemaConnectionRetryPolicy(int retriesPerPhase)
{
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
retriesPerPhase,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultSchemaMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
policy.RetryOccurred += DataConnectionFailureRetry;
return policy;
}
/// <summary>
/// Creates and returns a default Retry Policy for Schema based command operations.
/// </summary>
/// <remarks>The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry. </remarks>
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
public static RetryPolicy CreateSchemaCommandRetryPolicy(int retriesPerPhase)
{
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
retriesPerPhase,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultSchemaMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
policy.FastFirstRetry = false;
policy.RetryOccurred += CommandFailureRetry;
return policy;
}
/// <summary>
/// Creates and returns a Retry Policy for database creation operations.
/// </summary>
/// <param name="ignorableErrorNumbers">Errors to ignore if they occur after first retry</param>
/// <remarks>
/// The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry.
/// The IgnoreErrorOccurred event is wired to raise an RaiseAmbientIgnoreMessage message for ignore.
/// </remarks>
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
public static RetryPolicy CreateDatabaseCommandRetryPolicy(params int[] ignorableErrorNumbers)
{
RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy errorDetectionStrategy =
new RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(ignorableErrorNumbers);
// 30, 60, 60, 60, 60 second retries
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
errorDetectionStrategy,
RetryPolicyDefaults.DefaultCreateDatabaseRetryCount /* maxRetryCount */,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
TimeSpan.FromSeconds(30) /* minInterval */,
TimeSpan.FromSeconds(60) /* maxInterval */);
policy.FastFirstRetry = false;
policy.RetryOccurred += CreateDatabaseCommandFailureRetry;
policy.IgnoreErrorOccurred += CreateDatabaseCommandFailureIgnore;
return policy;
}
/// <summary>
/// Creates and returns an "ignoreable" command Retry Policy.
/// </summary>
/// <param name="ignorableErrorNumbers">Errors to ignore if they occur after first retry</param>
/// <remarks>
/// The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry.
/// The IgnoreErrorOccurred event is wired to raise an RaiseAmbientIgnoreMessage message for ignore.
/// </remarks>
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
public static RetryPolicy CreateElementCommandRetryPolicy(params int[] ignorableErrorNumbers)
{
Debug.Assert(ignorableErrorNumbers != null);
RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy errorDetectionStrategy =
new RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(ignorableErrorNumbers);
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
errorDetectionStrategy,
RetryPolicyDefaults.DefaulSchemaRetryCount,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultSchemaMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
policy.FastFirstRetry = false;
policy.RetryOccurred += ElementCommandFailureRetry;
policy.IgnoreErrorOccurred += ElementCommandFailureIgnore;
return policy;
}
/// <summary>
/// Creates and returns an "primary key violation" command Retry Policy.
/// </summary>
/// <param name="ignorableErrorNumbers">Errors to ignore if they occur after first retry</param>
/// <remarks>
/// The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry.
/// The IgnoreErrorOccurred event is wired to raise an RaiseAmbientIgnoreMessage message for ignore.
/// </remarks>
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
public static RetryPolicy CreatePrimaryKeyCommandRetryPolicy()
{
RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy errorDetectionStrategy =
new RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(SqlErrorNumbers.PrimaryKeyViolationErrorNumber);
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
errorDetectionStrategy,
RetryPolicyDefaults.DefaulSchemaRetryCount,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultSchemaMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
policy.FastFirstRetry = true;
policy.RetryOccurred += CommandFailureRetry;
policy.IgnoreErrorOccurred += CommandFailureIgnore;
return policy;
}
/// <summary>
/// Creates a Policy that will never allow retries to occur.
/// </summary>
/// <returns></returns>
public static RetryPolicy CreateNoRetryPolicy()
{
return new RetryPolicy.FixedDelayPolicy(TransientErrorIgnoreStrategy.Instance, 0, TimeSpan.Zero);
}
/// <summary>
/// Creates a Policy that is optimized for data-related script update operations.
/// This is extremely error tolerant and uses a Time based delay policy that backs
/// off until some overall length of delay has occurred. It is not as long-running
/// as the ConnectionManager data transfer retry policy since that's intended for bulk upload
/// of large amounts of data, whereas this is for individual batch scripts executed by the
/// batch execution engine.
/// </summary>
/// <returns></returns>
public static RetryPolicy CreateDataScriptUpdateRetryPolicy()
{
return new RetryPolicy.TimeBasedRetryPolicy(
RetryPolicy.DataTransferErrorDetectionStrategy.Instance,
TimeSpan.FromMinutes(7),
TimeSpan.FromMinutes(7),
0.1,
TimeSpan.FromMilliseconds(250),
TimeSpan.FromSeconds(30),
1.5);
}
/// <summary>
/// Returns the default retry policy dedicated to handling exceptions with SQL connections
/// </summary>
/// <returns>The RetryPolicy policy</returns>
public static RetryPolicy CreateFastDataRetryPolicy()
{
RetryPolicy retryPolicy = new RetryPolicy.FixedDelayPolicy(
RetryPolicy.NetworkConnectivityErrorDetectionStrategy.Instance,
RetryPolicyDefaults.DefaultDataCommandRetryCount,
TimeSpan.FromMilliseconds(5));
retryPolicy.FastFirstRetry = true;
retryPolicy.RetryOccurred += DataConnectionFailureRetry;
return retryPolicy;
}
/// <summary>
/// Returns the default retry policy dedicated to handling exceptions with SQL connections.
/// No logging or other message handler is attached to the policy
/// </summary>
/// <returns>The RetryPolicy policy</returns>
public static RetryPolicy CreateDefaultSchemaConnectionRetryPolicy()
{
return CreateDefaultConnectionRetryPolicy();
}
/// <summary>
/// Returns the default retry policy dedicated to handling exceptions with SQL connections.
/// Adds an event handler to log and notify listeners of data connection retries
/// </summary>
/// <returns>The RetryPolicy policy</returns>
public static RetryPolicy CreateDefaultDataConnectionRetryPolicy()
{
RetryPolicy retryPolicy = CreateDefaultConnectionRetryPolicy();
retryPolicy.RetryOccurred += DataConnectionFailureRetry;
return retryPolicy;
}
/// <summary>
/// Returns the default retry policy dedicated to handling exceptions with SQL connections
/// </summary>
/// <returns>The RetryPolicy policy</returns>
public static RetryPolicy CreateDefaultConnectionRetryPolicy()
{
// Note: No longer use Ado.net Connection Pooling and hence do not need TimeBasedRetryPolicy to
// conform to the backoff requirements in this case
RetryPolicy retryPolicy = new RetryPolicy.ExponentialDelayRetryPolicy(
RetryPolicy.NetworkConnectivityErrorDetectionStrategy.Instance,
RetryPolicyDefaults.DefaultConnectionRetryCount,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultSchemaMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
retryPolicy.FastFirstRetry = true;
return retryPolicy;
}
/// <summary>
/// Returns the default retry policy dedicated to handling retryable conditions with data transfer SQL commands.
/// </summary>
/// <returns>The RetryPolicy policy</returns>
public static RetryPolicy CreateDefaultDataSqlCommandRetryPolicy()
{
RetryPolicy retryPolicy = new RetryPolicy.ExponentialDelayRetryPolicy(
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
RetryPolicyDefaults.DefaultDataCommandRetryCount,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultDataMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
retryPolicy.FastFirstRetry = true;
retryPolicy.RetryOccurred += CommandFailureRetry;
return retryPolicy;
}
/// <summary>
/// Returns the default retry policy dedicated to handling retryable conditions with data transfer SQL commands.
/// </summary>
/// <returns>The RetryPolicy policy</returns>
public static RetryPolicy CreateDefaultDataTransferRetryPolicy()
{
RetryPolicy retryPolicy = new RetryPolicy.TimeBasedRetryPolicy(
RetryPolicy.DataTransferErrorDetectionStrategy.Instance,
TimeSpan.FromMinutes(20),
TimeSpan.FromMinutes(240),
0.1,
TimeSpan.FromMilliseconds(250),
TimeSpan.FromMinutes(2),
2);
retryPolicy.FastFirstRetry = true;
retryPolicy.RetryOccurred += CommandFailureRetry;
return retryPolicy;
}
/// <summary>
/// Returns the retry policy to handle data migration for column encryption.
/// </summary>
/// <returns>The RetryPolicy policy</returns>
public static RetryPolicy CreateColumnEncryptionTransferRetryPolicy()
{
RetryPolicy retryPolicy = new RetryPolicy.TimeBasedRetryPolicy(
RetryPolicy.DataTransferErrorDetectionStrategy.Instance,
TimeSpan.FromMinutes(5),
TimeSpan.FromMinutes(5),
0.1,
TimeSpan.FromMilliseconds(250),
TimeSpan.FromMinutes(2),
2);
retryPolicy.FastFirstRetry = true;
retryPolicy.RetryOccurred += CommandFailureRetry;
return retryPolicy;
}
public static void DataConnectionFailureRetry(RetryState retryState)
{
Logger.Write(TraceEventType.Information, string.Format(CultureInfo.InvariantCulture,
"Connection retry number {0}. Delaying {1} ms before retry. Exception: {2}",
retryState.RetryCount,
retryState.Delay.TotalMilliseconds.ToString(CultureInfo.InvariantCulture),
retryState.LastError.ToString()));
RetryPolicyUtils.RaiseAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.ConnectionRetry);
}
public static void CommandFailureRetry(RetryState retryState, string commandKeyword)
{
Logger.Write(TraceEventType.Information, string.Format(
CultureInfo.InvariantCulture,
"{0} retry number {1}. Delaying {2} ms before retry. Exception: {3}",
commandKeyword,
retryState.RetryCount,
retryState.Delay.TotalMilliseconds.ToString(CultureInfo.InvariantCulture),
retryState.LastError.ToString()));
RetryPolicyUtils.RaiseAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry);
}
public static void CommandFailureIgnore(RetryState retryState, string commandKeyword)
{
Logger.Write(TraceEventType.Information, string.Format(
CultureInfo.InvariantCulture,
"{0} retry number {1}. Ignoring failure. Exception: {2}",
commandKeyword,
retryState.RetryCount,
retryState.LastError.ToString()));
RetryPolicyUtils.RaiseAmbientIgnoreMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry);
}
public static void CommandFailureRetry(RetryState retryState)
{
CommandFailureRetry(retryState, "Command");
}
public static void CommandFailureIgnore(RetryState retryState)
{
CommandFailureIgnore(retryState, "Command");
}
public static void CreateDatabaseCommandFailureRetry(RetryState retryState)
{
CommandFailureRetry(retryState, "Database Command");
}
public static void CreateDatabaseCommandFailureIgnore(RetryState retryState)
{
CommandFailureIgnore(retryState, "Database Command");
}
public static void ElementCommandFailureRetry(RetryState retryState)
{
CommandFailureRetry(retryState, "Element Command");
}
public static void ElementCommandFailureIgnore(RetryState retryState)
{
CommandFailureIgnore(retryState, "Element Command");
}
}
}

View File

@@ -0,0 +1,486 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public static class RetryPolicyUtils
{
/// <summary>
/// Approved list of transient errors that should be retryable during Network connection stages
/// </summary>
private static readonly HashSet<int> _retryableNetworkConnectivityErrors;
/// <summary>
/// Approved list of transient errors that should be retryable on Azure
/// </summary>
private static readonly HashSet<int> _retryableAzureErrors;
/// <summary>
/// Blocklist of non-transient errors that should stop retry during data transfer operations
/// </summary>
private static readonly HashSet<int> _nonRetryableDataTransferErrors;
static RetryPolicyUtils()
{
_retryableNetworkConnectivityErrors = new HashSet<int>
{
/// A severe error occurred on the current command. The results, if any, should be discarded.
0,
//// DBNETLIB Error Code: 20
//// The instance of SQL Server you attempted to connect to does not support encryption.
(int) ProcessNetLibErrorCode.EncryptionNotSupported,
//// DBNETLIB Error Code: -2
//// Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
(int)ProcessNetLibErrorCode.Timeout,
//// SQL Error Code: 64
//// A connection was successfully established with the server, but then an error occurred during the login process.
//// (provider: TCP Provider, error: 0 - The specified network name is no longer available.)
64,
//// SQL Error Code: 233
//// The client was unable to establish a connection because of an error during connection initialization process before login.
//// Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy
//// to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server.
//// (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.)
233,
//// SQL Error Code: 10053
//// A transport-level error has occurred when receiving results from the server.
//// An established connection was aborted by the software in your host machine.
10053,
//// SQL Error Code: 10054
//// A transport-level error has occurred when sending the request to the server.
//// (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.)
10054,
//// SQL Error Code: 10060
//// A network-related or instance-specific error occurred while establishing a connection to SQL Server.
//// The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server
//// is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed
//// because the connected party did not properly respond after a period of time, or established connection failed
//// because connected host has failed to respond.)"}
10060,
// SQL Error Code: 11001
// A network-related or instance-specific error occurred while establishing a connection to SQL Server.
// The server was not found or was not accessible. Verify that the instance name is correct and that SQL
// Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - No such host is known.)
11001,
//// SQL Error Code: 40613
//// Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer
//// support, and provide them the session tracing ID of ZZZZZ.
40613,
};
_retryableAzureErrors = new HashSet<int>
{
//// SQL Error Code: 40
//// Could not open a connection to SQL Server
//// (provider: Named Pipes Provider, error: 40 Could not open a connection to SQL Server)
40,
//// SQL Error Code: 121
//// A transport-level error has occurred when receiving results from the server.
//// (provider: TCP Provider, error: 0 - The semaphore timeout period has expired.)
121,
//// SQL Error Code: 913 (noticed intermittently on SNAP runs with connected unit tests)
//// Could not find database ID %d. Database may not be activated yet or may be in transition. Reissue the query once the database is available.
//// If you do not think this error is due to a database that is transitioning its state and this error continues to occur, contact your primary support provider.
//// Please have available for review the Microsoft SQL Server error log and any additional information relevant to the circumstances when the error occurred.
913,
//// SQL Error Code: 1205
//// Transaction (Process ID %d) was deadlocked on %.*ls resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
1205,
//// SQL Error Code: 40501
//// The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded).
RetryPolicy.ThrottlingReason.ThrottlingErrorNumber,
//// SQL Error Code: 10928
//// Resource ID: %d. The %s limit for the database is %d and has been reached.
10928,
//// SQL Error Code: 10929
//// Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d.
//// However, the server is currently too busy to support requests greater than %d for this database.
10929,
//// SQL Error Code: 40143
//// The service has encountered an error processing your request. Please try again.
40143,
//// SQL Error Code: 40197
//// The service has encountered an error processing your request. Please try again.
40197,
//// Sql Error Code: 40549 (not supposed to be used anymore as of Q2 2011)
//// Session is terminated because you have a long-running transaction. Try shortening your transaction.
40549,
//// Sql Error Code: 40550 (not supposed to be used anymore as of Q2 2011)
//// The session has been terminated because it has acquired too many locks. Try reading or modifying fewer rows in a single transaction.
40550,
//// Sql Error Code: 40551 (not supposed to be used anymore as of Q2 2011)
//// The session has been terminated because of excessive TEMPDB usage. Try modifying your query to reduce the temporary table space usage.
40551,
//// Sql Error Code: 40552 (not supposed to be used anymore as of Q2 2011)
//// The session has been terminated because of excessive transaction log space usage. Try modifying fewer rows in a single transaction.
40552,
//// Sql Error Code: 40553 (not supposed to be used anymore as of Q2 2011)
//// The session has been terminated because of excessive memory usage. Try modifying your query to process fewer rows.
40553,
//// SQL Error Code: 40627
//// Operation on server YYY and database XXX is in progress. Please wait a few minutes before trying again.
40627,
//// SQL Error Code: 40671 (DB CRUD)
//// Unable to '%.*ls' '%.*ls' on server '%.*ls'. Please retry the connection later.
40671,
//// SQL Error Code: 40676 (DB CRUD)
//// '%.*ls' request was received but may not be processed completely at this time,
//// please query the sys.dm_operation_status table in the master database for status.
40676,
//// SQL Error Code: 45133
//// A connection failed while the operation was still in progress, and the outcome of the operation is unknown.
45133,
};
foreach(int errorNum in _retryableNetworkConnectivityErrors)
{
_retryableAzureErrors.Add(errorNum);
}
_nonRetryableDataTransferErrors = new HashSet<int>
{
//// Syntax error
156,
//// Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'. The duplicate key value is %ls.
2601,
//// Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'. The duplicate key value is %ls.
2627,
//// Cannot find index '%.*ls'.
2727,
//// SqlClr stack error
6522,
//// Divide by zero error encountered.
8134,
//// Could not repair this error.
8922,
//// Bug 1110540: This error means the table is corrupted due to hardware failure, so we do not want to retry.
//// Table error: Object ID %d. The text, ntext, or image node at page %S_PGID, slot %d, text ID %I64d is referenced by page %S_PGID, slot %d, but was not seen in the scan.
8965,
//// The query processor is unable to produce a plan because the clustered index is disabled.
8655,
//// The query processor is unable to produce a plan because table is unavailable because the heap is corrupted
8674,
//// SqlClr permission / load error.
//// Example Message: An error occurred in the Microsoft .NET Framework while trying to load assembly
10314,
//// '%ls' is not supported in this version of SQL Server.
40514,
//// The database 'XYZ' has reached its size quota. Partition or delete data, drop indexes, or consult the documentation for possible resolutions
40544,
};
}
public static bool IsRetryableNetworkConnectivityError(int errorNumber)
{
// .NET core has a bug on OSX/Linux that makes this error number always zero (issue 12472)
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return errorNumber != 0 && _retryableNetworkConnectivityErrors.Contains(errorNumber);
}
return _retryableNetworkConnectivityErrors.Contains(errorNumber);
}
public static bool IsRetryableAzureError(int errorNumber)
{
return _retryableAzureErrors.Contains(errorNumber) || _retryableNetworkConnectivityErrors.Contains(errorNumber);
}
public static bool IsNonRetryableDataTransferError(int errorNumber)
{
return _nonRetryableDataTransferErrors.Contains(errorNumber);
}
public static void AppendThrottlingDataIfIsThrottlingError(SqlException sqlException, SqlError error)
{
//// SQL Error Code: 40501
//// The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded).
if(error.Number == RetryPolicy.ThrottlingReason.ThrottlingErrorNumber)
{
// Decode the reason code from the error message to determine the grounds for throttling.
var condition = RetryPolicy.ThrottlingReason.FromError(error);
// Attach the decoded values as additional attributes to the original SQL exception.
sqlException.Data[condition.ThrottlingMode.GetType().Name] = condition.ThrottlingMode.ToString();
sqlException.Data[condition.GetType().Name] = condition;
}
}
/// <summary>
/// Calculates the length of time to delay a retry based on the number of retries up to this point.
/// As the number of retries increases, the timeout increases exponentially based on the intervalFactor.
/// Uses default values for the intervalFactor (<see cref="RetryPolicyDefaults.DefaultBackoffIntervalFactor"/>), minInterval
/// (<see cref="RetryPolicyDefaults.DefaultSchemaMinInterval"/>) and maxInterval (<see cref="RetryPolicyDefaults.DefaultMaxRetryInterval"/>)
/// </summary>
/// <param name="currentRetryCount">Total number of retries including the current retry</param>
/// <returns>TimeSpan defining the length of time to delay</returns>
public static TimeSpan CalcExponentialRetryDelayWithSchemaDefaults(int currentRetryCount)
{
return CalcExponentialRetryDelay(currentRetryCount,
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
RetryPolicyDefaults.DefaultSchemaMinInterval,
RetryPolicyDefaults.DefaultMaxRetryInterval);
}
/// <summary>
/// Calculates the length of time to delay a retry based on the number of retries up to this point.
/// As the number of retries increases, the timeout increases exponentially based on the intervalFactor.
/// A very large retry count can cause huge delay, so the maxInterval is used to cap delay time at a sensible
/// upper bound
/// </summary>
/// <param name="currentRetryCount">Total number of retries including the current retry</param>
/// <param name="intervalFactor">Controls the speed at which the delay increases - the retryCount is raised to this power as
/// part of the function </param>
/// <param name="minInterval">Minimum interval between retries. The basis for all backoff calculations</param>
/// <param name="maxInterval">Maximum interval between retries. Backoff will not take longer than this period.</param>
/// <returns>TimeSpan defining the length of time to delay</returns>
public static TimeSpan CalcExponentialRetryDelay(int currentRetryCount, double intervalFactor, TimeSpan minInterval, TimeSpan maxInterval)
{
try
{
return checked(TimeSpan.FromMilliseconds(
Math.Max(
Math.Min(
Math.Pow(intervalFactor, currentRetryCount - 1) * minInterval.TotalMilliseconds,
maxInterval.TotalMilliseconds
),
minInterval.TotalMilliseconds)
));
}
catch (OverflowException)
{
// If numbers are too large, could conceivably overflow the double.
// Since the maxInterval is the largest TimeSpan expected, can safely return this here
return maxInterval;
}
}
public static void RaiseAmbientRetryMessage(RetryState retryState, int errorCode)
{
Action<SqlServerRetryError> retryMsgHandler = AmbientSettings.ConnectionRetryMessageHandler;
if (retryMsgHandler != null)
{
string msg = SqlServerRetryError.FormatRetryMessage(
retryState.RetryCount,
retryState.Delay,
retryState.LastError);
retryMsgHandler(new SqlServerRetryError(
msg,
retryState.LastError,
retryState.RetryCount,
errorCode,
ErrorSeverity.Warning));
}
}
public static void RaiseAmbientIgnoreMessage(RetryState retryState, int errorCode)
{
Action<SqlServerRetryError> retryMsgHandler = AmbientSettings.ConnectionRetryMessageHandler;
if (retryMsgHandler != null)
{
string msg = SqlServerRetryError.FormatIgnoreMessage(
retryState.RetryCount,
retryState.LastError);
retryMsgHandler(new SqlServerRetryError(
msg,
retryState.LastError,
retryState.RetryCount,
errorCode,
ErrorSeverity.Warning));
}
}
/// <summary>
/// Traces the Schema retry information before raising the retry message
/// </summary>
/// <param name="retryState"></param>
/// <param name="errorCode"></param>
/// <param name="azureSessionId"></param>
public static void RaiseSchemaAmbientRetryMessage(RetryState retryState, int errorCode, Guid azureSessionId)
{
if (azureSessionId != Guid.Empty)
{
Logger.Write(TraceEventType.Warning, string.Format(
"Retry occurred: session: {0}; attempt - {1}; delay - {2}; exception - \"{3}\"",
azureSessionId,
retryState.RetryCount,
retryState.Delay,
retryState.LastError
));
RaiseAmbientRetryMessage(retryState, errorCode);
}
}
#region ProcessNetLibErrorCode enumeration
/// <summary>
/// Error codes reported by the DBNETLIB module.
/// </summary>
internal enum ProcessNetLibErrorCode
{
/// <summary>
/// Zero bytes were returned
/// </summary>
ZeroBytes = -3,
/// <summary>
/// Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
/// </summary>
Timeout = -2,
/// <summary>
/// An unknown net lib error
/// </summary>
Unknown = -1,
/// <summary>
/// Out of memory
/// </summary>
InsufficientMemory = 1,
/// <summary>
/// User or machine level access denied
/// </summary>
AccessDenied = 2,
/// <summary>
/// Connection was already busy processing another request
/// </summary>
ConnectionBusy = 3,
/// <summary>
/// The connection was broken without a proper disconnect
/// </summary>
ConnectionBroken = 4,
/// <summary>
/// The connection has reached a limit
/// </summary>
ConnectionLimit = 5,
/// <summary>
/// Name resolution failed for the given server name
/// </summary>
ServerNotFound = 6,
/// <summary>
/// Network transport could not be found
/// </summary>
NetworkNotFound = 7,
/// <summary>
/// A resource required could not be allocated
/// </summary>
InsufficientResources = 8,
/// <summary>
/// Network stack denied the request as too busy
/// </summary>
NetworkBusy = 9,
/// <summary>
/// Unable to access the requested network
/// </summary>
NetworkAccessDenied = 10,
/// <summary>
/// Internal error
/// </summary>
GeneralError = 11,
/// <summary>
/// The network mode was set incorrectly
/// </summary>
IncorrectMode = 12,
/// <summary>
/// The given name was not found
/// </summary>
NameNotFound = 13,
/// <summary>
/// Connection was invalid
/// </summary>
InvalidConnection = 14,
/// <summary>
/// A read or write error occurred
/// </summary>
ReadWriteError = 15,
/// <summary>
/// Unable to allocate an additional handle
/// </summary>
TooManyHandles = 16,
/// <summary>
/// The server reported an error
/// </summary>
ServerError = 17,
/// <summary>
/// SSL failed
/// </summary>
SSLError = 18,
/// <summary>
/// Encryption failed with an error
/// </summary>
EncryptionError = 19,
/// <summary>
/// Remote endpoint does not support encryption
/// </summary>
EncryptionNotSupported = 20
}
#endregion
}
}

View File

@@ -0,0 +1,86 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public class RetryState
{
private int _retryCount = 0;
private TimeSpan _delay = TimeSpan.Zero;
private Exception _lastError = null;
private bool _isDelayDisabled = false;
/// <summary>
/// Gets or sets the current retry attempt count.
/// </summary>
public int RetryCount
{
get
{
return _retryCount;
}
set
{
_retryCount = value;
}
}
/// <summary>
/// Gets or sets the delay indicating how long the current thread will be suspended for before the next iteration will be invoked.
/// </summary>
public TimeSpan Delay
{
get
{
return _delay;
}
set
{
_delay = value;
}
}
/// <summary>
/// Gets or sets the exception which caused the retry conditions to occur.
/// </summary>
public Exception LastError
{
get
{
return _lastError;
}
set
{
_lastError = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether we should ignore delay in order to be able to execute our tests faster
/// </summary>
/// <remarks>Intended for test use ONLY</remarks>
internal bool IsDelayDisabled
{
get
{
return _isDelayDisabled;
}
set
{
_isDelayDisabled = value;
}
}
public virtual void Reset()
{
this.IsDelayDisabled = false;
this.RetryCount = 0;
this.Delay = TimeSpan.Zero;
this.LastError = null;
}
}
}

View File

@@ -0,0 +1,53 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
static class SqlConnectionHelperScripts
{
public const string EngineEdition = "SELECT SERVERPROPERTY('EngineEdition'), SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'), SERVERPROPERTY ('MachineName'), (SELECT CASE WHEN EXISTS (SELECT TOP 1 1 from [sys].[all_columns] WITH (NOLOCK) WHERE name = N'xml_index_type' AND OBJECT_ID(N'sys.xml_indexes') = object_id) THEN 1 ELSE 0 END AS SXI_PRESENT)";
public const string EngineEditionWithLock = "SELECT SERVERPROPERTY('EngineEdition'), SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'), SERVERPROPERTY ('MachineName'), (SELECT CASE WHEN EXISTS (SELECT TOP 1 1 from [sys].[all_columns] WHERE name = N'xml_index_type' AND OBJECT_ID(N'sys.xml_indexes') = object_id) THEN 1 ELSE 0 END AS SXI_PRESENT)";
public const string CheckDatabaseReadonly = @"EXEC sp_dboption '{0}', 'read only'";
public const string GetDatabaseFilePathAndName = @"
DECLARE @filepath nvarchar(260),
@rc int
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',N'Software\Microsoft\MSSQLServer\MSSQLServer',N'DefaultData', @filepath output, 'no_output'
IF ((@filepath IS NOT NULL) AND (CHARINDEX(N'\', @filepath, len(@filepath)) = 0))
SELECT @filepath = @filepath + N'\'
IF (@filepath IS NULL)
SELECT @filepath = [sdf].[physical_name]
FROM [master].[sys].[database_files] AS [sdf]
WHERE [file_id] = 1
SELECT @filepath AS FilePath
";
public const string GetDatabaseLogPathAndName = @"
DECLARE @filepath nvarchar(260),
@rc int
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',N'Software\Microsoft\MSSQLServer\MSSQLServer',N'DefaultLog', @filepath output, 'no_output'
IF ((@filepath IS NOT NULL) AND (CHARINDEX(N'\', @filepath, len(@filepath)) = 0))
SELECT @filepath = @filepath + N'\'
IF (@filepath IS NULL)
SELECT @filepath = [ldf].[physical_name]
FROM [master].[sys].[database_files] AS [ldf]
WHERE [file_id] = 2
SELECT @filepath AS FilePath
";
public const string GetOsVersion = @"SELECT OSVersion = RIGHT(@@version, LEN(@@version)- 3 -charindex (' ON ', @@version))";
public const string GetClusterEndpoints = @"IF OBJECT_ID (N'master.dbo.cluster_endpoint_info') IS NOT NULL
SELECT [service_name], [ip_address], [port] FROM [master].[dbo].[cluster_endpoint_info];";
}
}

View File

@@ -0,0 +1,32 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Constants for SQL Error numbers
/// </summary>
internal static class SqlErrorNumbers
{
// Database XYZ already exists. Choose a different database name.
internal const int DatabaseAlreadyExistsErrorNumber = 1801;
// Cannot drop the database 'x', because it does not exist or you do not have permission.
internal const int DatabaseAlreadyDroppedErrorNumber = 3701;
// Database 'x' was created\altered successfully, but some properties could not be displayed.
internal const int DatabaseCrudMetadataUpdateErrorNumber = 45166;
// Violation of PRIMARY KEY constraint 'x'.
// Cannot insert duplicate key in object 'y'. The duplicate key value is (z).
internal const int PrimaryKeyViolationErrorNumber = 2627;
// There is already an object named 'x' in the database.
internal const int ObjectAlreadyExistsErrorNumber = 2714;
// Cannot drop the object 'x', because it does not exist or you do not have permission.
internal const int ObjectAlreadyDroppedErrorNumber = 3701;
}
}

View File

@@ -0,0 +1,465 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
public static class SqlSchemaModelErrorCodes
{
private const int ParserErrorCodeStartIndex = 46000;
private const int ParserErrorCodeEndIndex = 46499;
public static bool IsParseErrorCode(int errorCode)
{
return
(errorCode >= ParserErrorCodeStartIndex) &&
(errorCode <= ParserErrorCodeEndIndex);
}
public static bool IsInterpretationErrorCode(int errorCode)
{
return
(errorCode >= Interpretation.InterpretationBaseCode) &&
(errorCode <= Interpretation.InterpretationEndCode);
}
public static bool IsStatementFilterError(int errorCode)
{
return
(errorCode > StatementFilter.StatementFilterBaseCode) &&
(errorCode <= StatementFilter.StatementFilterMaxErrorCode);
}
public static class StatementFilter
{
public const int StatementFilterBaseCode = 70000;
public const int UnrecognizedStatement = StatementFilterBaseCode + 1;
public const int ServerObject = StatementFilterBaseCode + 2;
public const int AtMostTwoPartName = StatementFilterBaseCode + 3;
public const int AlterTableAddColumn = StatementFilterBaseCode + 4;
public const int ConstraintAll = StatementFilterBaseCode + 5;
public const int TriggerAll = StatementFilterBaseCode + 6;
public const int CreateSchemaWithoutName = StatementFilterBaseCode + 7;
public const int CreateSchemaElements = StatementFilterBaseCode + 8;
public const int AlterAssembly = StatementFilterBaseCode + 9;
public const int CreateStoplist = StatementFilterBaseCode + 10;
public const int UnsupportedPermission = StatementFilterBaseCode + 11;
public const int TopLevelExecuteWithResultSets = StatementFilterBaseCode + 12;
public const int AlterTableAddConstraint = StatementFilterBaseCode + 13;
public const int DatabaseOnlyObjectInServerProject = StatementFilterBaseCode + 14;
public const int UnsupportedBySqlAzure = StatementFilterBaseCode + 15;
public const int UnsupportedSecurityObjectKind = StatementFilterBaseCode + 16;
public const int StatementNotSupportedForCurrentRelease = StatementFilterBaseCode + 17;
public const int ServerPermissionsNotAllowed = StatementFilterBaseCode + 18;
public const int DeprecatedSyntax = StatementFilterBaseCode + 19;
public const int SetRemoteData = StatementFilterBaseCode + 20;
public const int StatementFilterMaxErrorCode = StatementFilterBaseCode + 499;
}
public static class Interpretation
{
public const int InterpretationBaseCode = 70500;
public const int InvalidTopLevelStatement = InterpretationBaseCode + 1;
public const int InvalidAssemblySource = InterpretationBaseCode + 2;
public const int InvalidDatabaseName = InterpretationBaseCode + 3;
public const int OnlyTwoPartNameAllowed = InterpretationBaseCode + 4;
public const int SecurityObjectCannotBeNull = InterpretationBaseCode + 5;
public const int UnknownPermission = InterpretationBaseCode + 6;
public const int UnsupportedAll = InterpretationBaseCode + 7;
public const int InvalidColumnList = InterpretationBaseCode + 8;
public const int ColumnsAreNotAllowed = InterpretationBaseCode + 9;
public const int InvalidDataType = InterpretationBaseCode + 10;
public const int InvalidObjectName = InterpretationBaseCode + 11;
public const int InvalidObjectChildName = InterpretationBaseCode + 12;
public const int NoGlobalTemporarySymmetricKey = InterpretationBaseCode + 13;
public const int NoGlobalTemporarySymmetricKey_Warning = InterpretationBaseCode + 14;
public const int NameCannotBeNull = InterpretationBaseCode + 15;
public const int NameCannotBeNull_Warning = InterpretationBaseCode + 16;
public const int InvalidLoginName = InterpretationBaseCode + 17;
public const int InvalidLoginName_Warning = InterpretationBaseCode + 18;
public const int MoreAliasesThanColumns = InterpretationBaseCode + 19;
public const int FewerAliasesThanColumns = InterpretationBaseCode + 20;
public const int InvalidTimestampReturnType = InterpretationBaseCode + 21;
public const int VariableParameterAtTopLevelStatement = InterpretationBaseCode + 22;
public const int CannotCreateTempTable = InterpretationBaseCode + 23;
public const int MultipleNullabilityConstraintError = InterpretationBaseCode + 24;
public const int MultipleNullabilityConstraintWarning = InterpretationBaseCode + 25;
public const int ColumnIsntAllowedForAssemblySource = InterpretationBaseCode + 26;
public const int InvalidUserName = InterpretationBaseCode + 27;
public const int InvalidWindowsLogin = InterpretationBaseCode + 28;
public const int InvalidWindowsLogin_Warning = InterpretationBaseCode + 29;
public const int CannotHaveUsingForPrimaryXmlIndex = InterpretationBaseCode + 30;
public const int UsingIsRequiredForSecondaryXmlIndex = InterpretationBaseCode + 31;
public const int XmlIndexTypeIsRequiredForSecondaryXmlIndex = InterpretationBaseCode + 32;
public const int UnsupportedAlterCryptographicProvider = InterpretationBaseCode + 33;
public const int HttpForSoapOnly = InterpretationBaseCode + 34;
public const int UnknownEventTypeOrGroup = InterpretationBaseCode + 35;
public const int CannotAddLogFileToFilegroup = InterpretationBaseCode + 36;
public const int BuiltInTypeExpected = InterpretationBaseCode + 37;
public const int MissingArgument = InterpretationBaseCode + 38;
public const int InvalidArgument = InterpretationBaseCode + 39;
public const int IncompleteBoundingBoxCoordinates = InterpretationBaseCode + 40;
public const int XMaxLessThanXMin = InterpretationBaseCode + 41;
public const int YMaxLessThanYMin = InterpretationBaseCode + 42;
public const int InvalidCoordinate = InterpretationBaseCode + 43;
public const int InvalidValue = InterpretationBaseCode + 44;
public const int InvalidIdentityValue = InterpretationBaseCode + 45;
public const int InvalidPriorityLevel = InterpretationBaseCode + 46;
public const int TriggerIsNotForEvent = InterpretationBaseCode + 47;
public const int SyntaxError = InterpretationBaseCode + 48;
public const int UnsupportedPintable = InterpretationBaseCode + 49;
public const int DuplicateEventType = InterpretationBaseCode + 50;
public const int ClearAndBasicAreNotAllowed = InterpretationBaseCode + 51;
public const int AssemblyCorruptErrorCode = InterpretationBaseCode + 57;
public const int DynamicQuery = InterpretationBaseCode + 58;
public const int OnlyLcidAllowed = InterpretationBaseCode + 59;
public const int WildCardNotAllowed = InterpretationBaseCode + 60;
public const int CannotBindSchema = InterpretationBaseCode + 61;
public const int TableTypeNotAllowFunctionCall = InterpretationBaseCode + 62;
public const int ColumnNotAllowed = InterpretationBaseCode + 63;
public const int OwnerRequiredForEndpoint = InterpretationBaseCode + 64;
public const int PartitionNumberMustBeInteger = InterpretationBaseCode + 65;
public const int DuplicatedPartitionNumber = InterpretationBaseCode + 66;
public const int FromPartitionGreaterThanToPartition = InterpretationBaseCode + 67;
public const int CannotSpecifyPartitionNumber = InterpretationBaseCode + 68;
public const int MissingColumnNameError = InterpretationBaseCode + 69;
public const int MissingColumnNameWarning = InterpretationBaseCode + 70;
public const int UnknownTableSourceError = InterpretationBaseCode + 71;
public const int UnknownTableSourceWarning = InterpretationBaseCode + 72;
public const int TooManyPartsForCteOrAliasError = InterpretationBaseCode + 73;
public const int TooManyPartsForCteOrAliasWarning = InterpretationBaseCode + 74;
public const int ServerAuditInvalidQueueDelayValue = InterpretationBaseCode + 75;
public const int WrongEventType = InterpretationBaseCode + 76;
public const int CantCreateUddtFromXmlError = InterpretationBaseCode + 77;
public const int CantCreateUddtFromXmlWarning = InterpretationBaseCode + 78;
public const int CantCreateUddtFromUddtError = InterpretationBaseCode + 79;
public const int CantCreateUddtFromUddtWarning = InterpretationBaseCode + 80;
public const int ForReplicationIsNotSupported = InterpretationBaseCode + 81;
public const int TooLongIdentifier = InterpretationBaseCode + 82;
public const int InvalidLanguageTerm = InterpretationBaseCode + 83;
public const int InvalidParameterOrOption = InterpretationBaseCode + 85;
public const int TableLevelForeignKeyWithNoColumnsError = InterpretationBaseCode + 86;
public const int TableLevelForeignKeyWithNoColumnsWarning = InterpretationBaseCode + 87;
public const int ConstraintEnforcementIsIgnored = InterpretationBaseCode + 88;
public const int DeprecatedBackupOption = InterpretationBaseCode + 89;
public const int UndeclaredVariableParameter = InterpretationBaseCode + 90;
public const int UnsupportedAlgorithm = InterpretationBaseCode + 91;
public const int InvalidLanguageNameOrAliasWarning = InterpretationBaseCode + 92;
public const int UnsupportedRevoke = InterpretationBaseCode + 93;
public const int InvalidPermissionTypeAgainstObject = InterpretationBaseCode + 94;
public const int InvalidPermissionObjectType = InterpretationBaseCode + 95;
public const int CannotDetermineSecurableFromPermission = InterpretationBaseCode + 96;
public const int InvalidColumnListForSecurableType = InterpretationBaseCode + 97;
public const int InvalidUserDefaultLanguage = InterpretationBaseCode + 98;
public const int CannotSpecifyGridParameterForAutoGridSpatialIndex = InterpretationBaseCode + 99;
public const int UnsupportedSpatialTessellationScheme = InterpretationBaseCode + 100;
public const int CannotSpecifyBoundingBoxForGeography = InterpretationBaseCode + 101;
public const int InvalidSearchPropertyId = InterpretationBaseCode + 102;
public const int OnlineSpatialIndex = InterpretationBaseCode + 103;
public const int SqlCmdVariableInObjectName = InterpretationBaseCode + 104;
public const int SubqueriesNotAllowed = InterpretationBaseCode + 105;
public const int ArgumentReplaceNotSupported = InterpretationBaseCode + 106;
public const int DuplicateArgument = InterpretationBaseCode + 107;
public const int UnsupportedNoPopulationChangeTrackingOption = InterpretationBaseCode + 108;
public const int UnsupportedResourceManagerLocationProperty = InterpretationBaseCode + 109;
public const int RequiredExternalDataSourceLocationPropertyMissing = InterpretationBaseCode + 110;
public const int UnsupportedSerdeMethodProperty = InterpretationBaseCode + 111;
public const int UnsupportedFormatOptionsProperty = InterpretationBaseCode + 112;
public const int RequiredSerdeMethodPropertyMissing = InterpretationBaseCode + 113;
public const int TableLevelIndexWithNoColumnsError = InterpretationBaseCode + 114;
public const int TableLevelIndexWithNoColumnsWarning = InterpretationBaseCode + 115;
public const int InvalidIndexOption = InterpretationBaseCode + 116;
public const int TypeAndSIDMustBeUsedTogether = InterpretationBaseCode + 117;
public const int TypeCannotBeUsedWithLoginOption = InterpretationBaseCode + 118;
public const int InvalidUserType = InterpretationBaseCode + 119;
public const int InvalidUserSid = InterpretationBaseCode + 120;
public const int InvalidPartitionFunctionDataType = InterpretationBaseCode + 121;
public const int RequiredExternalTableLocationPropertyMissing = InterpretationBaseCode + 122;
public const int UnsupportedRejectSampleValueProperty = InterpretationBaseCode + 123;
public const int RequiredExternalDataSourceDatabasePropertyMissing = InterpretationBaseCode + 124;
public const int RequiredExternalDataSourceShardMapNamePropertyMissing = InterpretationBaseCode + 125;
public const int InvalidPropertyForExternalDataSourceType = InterpretationBaseCode + 126;
public const int UnsupportedExternalDataSourceTypeInCurrentPlatform = InterpretationBaseCode + 127;
public const int UnsupportedExternalTableProperty = InterpretationBaseCode + 128;
public const int MaskingFunctionIsEmpty = InterpretationBaseCode + 129;
public const int InvalidMaskingFunctionFormat = InterpretationBaseCode + 130;
public const int CannotCreateAlwaysEncryptedObject = InterpretationBaseCode + 131;
public const int ExternalTableSchemaOrObjectNameMissing = InterpretationBaseCode + 132;
public const int CannotCreateTemporalTableWithoutHistoryTableName = InterpretationBaseCode + 133;
public const int TemporalPeriodColumnMustNotBeNullable = InterpretationBaseCode + 134;
public const int InterpretationEndCode = InterpretationBaseCode + 499;
}
public static class ModelBuilder
{
private const int ModelBuilderBaseCode = 71000;
public const int CannotFindMainElement = ModelBuilderBaseCode + 1;
public const int CannotFindColumnSourceGrantForColumnRevoke = ModelBuilderBaseCode + 2;
public const int AssemblyReferencesNotSupported = ModelBuilderBaseCode + 3;
public const int NoSourceForColumn = ModelBuilderBaseCode + 5;
public const int MoreThanOneStatementPerBatch = ModelBuilderBaseCode + 6;
public const int MaximumSizeExceeded = ModelBuilderBaseCode + 7;
}
public static class Validation
{
private const int ValidationBaseCode = 71500;
public const int AllReferencesMustBeResolved = ValidationBaseCode + 1;
public const int AllReferencesMustBeResolved_Warning = ValidationBaseCode + 2;
public const int AssemblyVisibilityRule = ValidationBaseCode + 3;
public const int BreakContinueOnlyInWhile = ValidationBaseCode + 4;
public const int ClrObjectAssemblyReference_InvalidAssembly = ValidationBaseCode + 5;
public const int ClrObjectAssemblyReference = ValidationBaseCode + 6;
public const int ColumnUserDefinedTableType = ValidationBaseCode + 7;
public const int DuplicateName = ValidationBaseCode + 8;
public const int DuplicateName_Warning = ValidationBaseCode + 9;
public const int DuplicateVariableParameterName_TemporaryTable = ValidationBaseCode + 10;
public const int DuplicateVariableParameterName_Variable = ValidationBaseCode + 11;
public const int EndPointRule_DATABASE_MIRRORING = ValidationBaseCode + 12;
public const int EndPointRule_SERVICE_BROKER = ValidationBaseCode + 13;
public const int ForeignKeyColumnTypeNumberMustMatch_NumberOfColumns = ValidationBaseCode + 14;
public const int ForeignKeyColumnTypeNumberMustMatch_TypeMismatch = ValidationBaseCode + 15;
public const int ForeignKeyReferencePKUnique = ValidationBaseCode + 16;
public const int FullTextIndexColumn = ValidationBaseCode + 17;
public const int IdentityColumnValidation_InvalidType = ValidationBaseCode + 18;
public const int IdentityColumnValidation_MoreThanOneIdentity = ValidationBaseCode + 19;
public const int InsertIntoIdentityColumn = ValidationBaseCode + 20;
public const int MatchingSignatureNotFoundInAssembly = ValidationBaseCode + 21;
public const int MatchingTypeNotFoundInAssembly = ValidationBaseCode + 22;
public const int MaxColumnInIndexKey = ValidationBaseCode + 25;
public const int MaxColumnInTable_1024Columns = ValidationBaseCode + 26;
public const int MultiFullTextIndexOnTable = ValidationBaseCode + 28;
public const int NonNullPrimaryKey_NonNullSimpleColumn = ValidationBaseCode + 29;
public const int NonNullPrimaryKey_NotPersistedComputedColumn = ValidationBaseCode + 30;
public const int OneClusteredIndex = ValidationBaseCode + 31;
public const int OneMasterKey = ValidationBaseCode + 32;
public const int OnePrimaryKey = ValidationBaseCode + 33;
public const int PrimaryXMLIndexClustered = ValidationBaseCode + 34;
public const int SelectAssignRetrieval = ValidationBaseCode + 35;
public const int SubroutineParameterReadOnly_NonUDTTReadOnly = ValidationBaseCode + 36;
public const int SubroutineParameterReadOnly_UDTTReadOnly = ValidationBaseCode + 37;
public const int UsingXMLIndex = ValidationBaseCode + 38;
public const int VardecimalOptionRule = ValidationBaseCode + 39;
public const int WildCardExpansion = ValidationBaseCode + 40;
public const int WildCardExpansion_Warning = ValidationBaseCode + 41;
public const int XMLIndexOnlyXMLTypeColumn = ValidationBaseCode + 42;
public const int TableVariablePrefix = ValidationBaseCode + 44;
public const int FileStream_FILESTREAMON = ValidationBaseCode + 45;
public const int FileStream_ROWGUIDCOLUMN = ValidationBaseCode + 46;
public const int MaxColumnInTable100_Columns = ValidationBaseCode + 47;
public const int XMLIndexOnlyXMLTypeColumn_SparseColumnSet = ValidationBaseCode + 48;
public const int ClrObjectAssemblyReference_ParameterTypeMismatch = ValidationBaseCode + 50;
public const int OneDefaultConstraintPerColumn = ValidationBaseCode + 51;
public const int PermissionStatementValidation_DuplicatePermissionOnSecurable = ValidationBaseCode + 52;
public const int PermissionStatementValidation_ConflictingPermissionsOnSecurable = ValidationBaseCode + 53;
public const int PermissionStatementValidation_ConflictingColumnStatements = ValidationBaseCode + 54;
public const int PermissionOnObjectSecurableValidation_InvalidPermissionForObject = ValidationBaseCode + 55;
public const int SequenceValueValidation_ValueOutOfRange = ValidationBaseCode + 56;
public const int SequenceValueValidation_InvalidDataType = ValidationBaseCode + 57;
public const int MismatchedName_Warning = ValidationBaseCode + 58;
public const int DifferentNameCasing_Warning = ValidationBaseCode + 59;
public const int OneClusteredIndexAzure = ValidationBaseCode + 60;
public const int AllExternalReferencesMustBeResolved = ValidationBaseCode + 61;
public const int AllExternalReferencesMustBeResolved_Warning = ValidationBaseCode + 62;
public const int ExternalObjectWildCardExpansion_Warning = ValidationBaseCode + 63;
public const int UnsupportedElementForDataPackage = ValidationBaseCode + 64;
public const int InvalidFileStreamOptions = ValidationBaseCode + 65;
public const int StorageShouldNotSetOnDifferentInstance = ValidationBaseCode + 66;
public const int TableShouldNotHaveStorage = ValidationBaseCode + 67;
public const int MemoryOptimizedObjectsValidation_NonMemoryOptimizedTableCannotBeAccessed = ValidationBaseCode + 68;
public const int MemoryOptimizedObjectsValidation_SyntaxNotSupportedOnHekatonElement = ValidationBaseCode + 69;
public const int MemoryOptimizedObjectsValidation_ValidatePrimaryKeyForSchemaAndDataTables = ValidationBaseCode + 70;
public const int MemoryOptimizedObjectsValidation_ValidatePrimaryKeyForSchemaOnlyTables = ValidationBaseCode + 71;
public const int MemoryOptimizedObjectsValidation_OnlyNotNullableColumnsOnIndexes = ValidationBaseCode + 72;
public const int MemoryOptimizedObjectsValidation_HashIndexesOnlyOnMemoryOptimizedObjects = ValidationBaseCode + 73;
public const int MemoryOptimizedObjectsValidation_OptionOnlyForHashIndexes = ValidationBaseCode + 74;
public const int IncrementalStatisticsValidation_FilterNotSupported = ValidationBaseCode + 75;
public const int IncrementalStatisticsValidation_ViewNotSupported = ValidationBaseCode + 76;
public const int IncrementalStatisticsValidation_IndexNotPartitionAligned = ValidationBaseCode + 77;
public const int AzureV12SurfaceAreaValidation = ValidationBaseCode + 78;
public const int DuplicatedTargetObjectReferencesInSecurityPolicy = ValidationBaseCode + 79;
public const int MultipleSecurityPoliciesOnTargetObject = ValidationBaseCode + 80;
public const int ExportedRowsMayBeIncomplete = ValidationBaseCode + 81;
public const int ExportedRowsMayContainSomeMaskedData = ValidationBaseCode + 82;
public const int EncryptedColumnValidation_EncryptedPrimaryKey = ValidationBaseCode + 83;
public const int EncryptedColumnValidation_EncryptedUniqueColumn = ValidationBaseCode + 84;
public const int EncryptedColumnValidation_EncryptedCheckConstraint = ValidationBaseCode + 85;
public const int EncryptedColumnValidation_PrimaryKeyForeignKeyEncryptionMismatch = ValidationBaseCode + 86;
public const int EncryptedColumnValidation_UnsupportedDataType = ValidationBaseCode + 87;
public const int MemoryOptimizedObjectsValidation_UnSupportedOption = ValidationBaseCode + 88;
public const int MasterKeyExistsForCredential = ValidationBaseCode + 89;
public const int MemoryOptimizedObjectsValidation_InvalidForeignKeyRelationship = ValidationBaseCode + 90;
public const int MemoryOptimizedObjectsValidation_UnsupportedForeignKeyReference = ValidationBaseCode + 91;
public const int EncryptedColumnValidation_RowGuidColumn = ValidationBaseCode + 92;
public const int EncryptedColumnValidation_EncryptedClusteredIndex = ValidationBaseCode + 93;
public const int EncryptedColumnValidation_EncryptedNonClusteredIndex = ValidationBaseCode + 94;
public const int EncryptedColumnValidation_DependentComputedColumn = ValidationBaseCode + 95;
public const int EncryptedColumnValidation_EncryptedFullTextColumn = ValidationBaseCode + 96;
public const int EncryptedColumnValidation_EncryptedSparseColumnSet = ValidationBaseCode + 97;
public const int EncryptedColumnValidation_EncryptedStatisticsColumn = ValidationBaseCode + 98;
public const int EncryptedColumnValidation_EncryptedPartitionColumn = ValidationBaseCode + 99;
public const int EncryptedColumnValidation_PrimaryKeyChangeTrackingColumn = ValidationBaseCode + 100;
public const int EncryptedColumnValidation_ChangeDataCaptureOn = ValidationBaseCode + 101;
public const int EncryptedColumnValidation_FilestreamColumn = ValidationBaseCode + 102;
public const int EncryptedColumnValidation_MemoryOptimizedTable = ValidationBaseCode + 103;
public const int EncryptedColumnValidation_MaskedEncryptedColumn = ValidationBaseCode + 104;
public const int EncryptedColumnValidation_EncryptedIdentityColumn = ValidationBaseCode + 105;
public const int EncryptedColumnValidation_EncryptedDefaultConstraint = ValidationBaseCode + 106;
public const int TemporalValidation_InvalidPeriodSpecification = ValidationBaseCode + 107;
public const int TemporalValidation_MultipleCurrentTables = ValidationBaseCode + 108;
public const int TemporalValidation_SchemaMismatch = ValidationBaseCode + 109;
public const int TemporalValidation_ComputedColumns = ValidationBaseCode + 110;
public const int TemporalValidation_NoAlwaysEncryptedCols = ValidationBaseCode + 111;
public const int IndexesOnExternalTable = ValidationBaseCode + 112;
public const int TriggersOnExternalTable = ValidationBaseCode + 113;
public const int StretchValidation_ExportBlocked = ValidationBaseCode + 114;
public const int StretchValidation_ImportBlocked = ValidationBaseCode + 115;
public const int DeploymentBlocked = ValidationBaseCode + 116;
public const int NoBlockPredicatesTargetingViews = ValidationBaseCode + 117;
public const int SchemaBindingOnSecurityPoliciesValidation = ValidationBaseCode + 118;
public const int SecurityPredicateTargetObjectValidation = ValidationBaseCode + 119;
public const int TemporalValidation_SchemaMismatch_ColumnCount = ValidationBaseCode + 120;
public const int AkvValidation_AuthenticationFailed = ValidationBaseCode + 121;
public const int TemporalValidation_PrimaryKey = ValidationBaseCode + 122;
}
public static class SqlMSBuild
{
private const int MSBuildBaseCode = 72000;
public const int FileDoesNotExist = MSBuildBaseCode + 1;
public const int UnknownDeployError = MSBuildBaseCode + 2;
public const int InvalidProperty = MSBuildBaseCode + 3;
public const int CollationError = MSBuildBaseCode + 4;
public const int InvalidSqlClrDefinition = MSBuildBaseCode + 5;
public const int SQL_PrePostFatalParserError = MSBuildBaseCode + 6;
public const int SQL_PrePostSyntaxCheckError = MSBuildBaseCode + 7;
public const int SQL_PrePostVariableError = MSBuildBaseCode + 8;
public const int SQL_CycleError = MSBuildBaseCode + 9;
public const int SQL_NoConnectionStringNoServerVerification = MSBuildBaseCode + 10;
public const int SQL_VardecimalMismatch = MSBuildBaseCode + 11;
public const int SQL_NoAlterFileSystemObject = MSBuildBaseCode + 12;
public const int SQL_SqlCmdVariableOverrideError = MSBuildBaseCode + 13;
public const int SQL_BatchError = MSBuildBaseCode + 14;
public const int SQL_DataLossError = MSBuildBaseCode + 15;
public const int SQL_ExecutionError = MSBuildBaseCode + 16;
public const int SQL_UncheckedConstraint = MSBuildBaseCode + 17;
public const int SQL_UnableToImportElements = MSBuildBaseCode + 18;
public const int SQL_TargetReadOnlyError = MSBuildBaseCode + 19;
public const int SQL_UnsupportedCompatibilityMode = MSBuildBaseCode + 20;
public const int SQL_IncompatibleDSPVersions = MSBuildBaseCode + 21;
public const int SQL_CouldNotLoadSymbols = MSBuildBaseCode + 22;
public const int SQL_ContainmentlMismatch = MSBuildBaseCode + 23;
public const int SQL_PrePostExpectedNoTSqlError = MSBuildBaseCode + 24;
public const int ReferenceErrorCode = MSBuildBaseCode + 25;
public const int FileError = MSBuildBaseCode + 26;
public const int MissingReference = MSBuildBaseCode + 27;
public const int SerializationError = MSBuildBaseCode + 28;
public const int DeploymentContributorVerificationError = MSBuildBaseCode + 29;
public const int Deployment_PossibleRuntimeError = MSBuildBaseCode + 30;
public const int Deployment_BlockingDependency = MSBuildBaseCode + 31;
public const int Deployment_TargetObjectLoss = MSBuildBaseCode + 32;
public const int Deployment_MissingDependency = MSBuildBaseCode + 33;
public const int Deployment_PossibleDataLoss = MSBuildBaseCode + 34;
public const int Deployment_NotSupportedOperation = MSBuildBaseCode + 35;
public const int Deployment_Information = MSBuildBaseCode + 36;
public const int Deployment_UnsupportedDSP = MSBuildBaseCode + 37;
public const int Deployment_SkipManagementScopedChange = MSBuildBaseCode + 38;
public const int StaticCodeAnalysis_GeneralException = MSBuildBaseCode + 39;
public const int StaticCodeAnalysis_ResultsFileIOException = MSBuildBaseCode + 40;
public const int StaticCodeAnalysis_FailToCreateTaskHost = MSBuildBaseCode + 41;
public const int StaticCodeAnalysis_InvalidDataSchemaModel = MSBuildBaseCode + 42;
public const int StaticCodeAnalysis_InvalidElement = MSBuildBaseCode + 43;
public const int Deployment_NoClusteredIndex = MSBuildBaseCode + 44;
public const int Deployment_DetailedScriptExecutionError = MSBuildBaseCode + 45;
}
public static class Refactoring
{
private const int RefactoringBaseCode = 72500;
public const int FailedToLoadFile = RefactoringBaseCode + 1;
}
/// <summary>
/// These codes are used to message specific actions for extract and deployment operations.
/// The primary consumer of these codes is the Import/Export service.
/// </summary>
public static class ServiceActions
{
public const int ServiceActionsBaseCode = 73000;
public const int ServiceActionsMaxCode = 73000 + 0xFF;
// Note: These codes are defined so that the lower 3 bits indicate one of three
// event stages: Started (0x01), Done/Complete (0x02), Done/Failed (0x04)
public const int DeployInitializeStart = ServiceActionsBaseCode + 0x01;
public const int DeployInitializeSuccess = ServiceActionsBaseCode + 0x02;
public const int DeployInitializeFailure = ServiceActionsBaseCode + 0x04;
public const int DeployAnalysisStart = ServiceActionsBaseCode + 0x11;
public const int DeployAnalysisSuccess = ServiceActionsBaseCode + 0x12;
public const int DeployAnalysisFailure = ServiceActionsBaseCode + 0x14;
public const int DeployExecuteScriptStart = ServiceActionsBaseCode + 0x21;
public const int DeployExecuteScriptSuccess = ServiceActionsBaseCode + 0x22;
public const int DeployExecuteScriptFailure = ServiceActionsBaseCode + 0x24;
public const int DataImportStart = ServiceActionsBaseCode + 0x41;
public const int DataImportSuccess = ServiceActionsBaseCode + 0x42;
public const int DataImportFailure = ServiceActionsBaseCode + 0x44;
public const int ExtractSchemaStart = ServiceActionsBaseCode + 0x61;
public const int ExtractSchemaSuccess = ServiceActionsBaseCode + 0x62;
public const int ExtractSchemaFailure = ServiceActionsBaseCode + 0x64;
public const int ExportVerifyStart = ServiceActionsBaseCode + 0x71;
public const int ExportVerifySuccess = ServiceActionsBaseCode + 0x72;
public const int ExportVerifyFailure = ServiceActionsBaseCode + 0x74;
public const int ExportDataStart = ServiceActionsBaseCode + 0x81;
public const int ExportDataSuccess = ServiceActionsBaseCode + 0x82;
public const int ExportDataFailure = ServiceActionsBaseCode + 0x84;
public const int EnableIndexesDataStart = ServiceActionsBaseCode + 0xb1;
public const int EnableIndexesDataSuccess = ServiceActionsBaseCode + 0xb2;
public const int EnableIndexesDataFailure = ServiceActionsBaseCode + 0xb4;
public const int DisableIndexesDataStart = ServiceActionsBaseCode + 0xc1;
public const int DisableIndexesDataSuccess = ServiceActionsBaseCode + 0xc2;
public const int DisableIndexesDataFailure = ServiceActionsBaseCode + 0xc4;
public const int EnableIndexDataStart = ServiceActionsBaseCode + 0xd1;
public const int EnableIndexDataSuccess = ServiceActionsBaseCode + 0xd2;
public const int EnableIndexDataFailure = ServiceActionsBaseCode + 0xd4;
public const int DisableIndexDataStart = ServiceActionsBaseCode + 0xe1;
public const int DisableIndexDataSuccess = ServiceActionsBaseCode + 0xe2;
public const int DisableIndexDataFailure = ServiceActionsBaseCode + 0xe4;
public const int ColumnEncryptionDataMigrationStart = ServiceActionsBaseCode + 0xf1;
public const int ColumnEncryptionDataMigrationSuccess = ServiceActionsBaseCode + 0xf2;
public const int ColumnEncryptionDataMigrationFailure = ServiceActionsBaseCode + 0xf4;
// These codes do not set the lower 3 bits
public const int ConnectionRetry = ServiceActionsBaseCode + 0x90;
public const int CommandRetry = ServiceActionsBaseCode + 0x91;
public const int GeneralProgress = ServiceActionsBaseCode + 0x92;
public const int TypeFidelityLoss = ServiceActionsBaseCode + 0x93;
public const int TableProgress = ServiceActionsBaseCode + 0x94;
public const int ImportBlocked = ServiceActionsBaseCode + 0x95;
public const int DataPrecisionLoss = ServiceActionsBaseCode + 0x96;
public const int DataRowCount = ServiceActionsBaseCode + 0x98;
public const int DataException = ServiceActionsBaseCode + 0xA0;
public const int LogEntry = ServiceActionsBaseCode + 0xA1;
public const int GeneralInfo = ServiceActionsBaseCode + 0xA2;
}
}
}

View File

@@ -0,0 +1,74 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Represents an error produced by SQL Server database schema provider
/// </summary>
[Serializable]
public class SqlServerError : DataSchemaError
{
private const string SqlServerPrefix = "SQL";
private const string DefaultHelpKeyword = "vs.teamsystem.datatools.DefaultErrorMessageHelp";
public SqlServerError(string message, string document, ErrorSeverity severity)
: this(message, null, document, 0, 0, Constants.UndefinedErrorCode, severity)
{
}
public SqlServerError(string message, string document, int errorCode, ErrorSeverity severity)
: this(message, null, document, 0, 0, errorCode, severity)
{
}
public SqlServerError(Exception exception, string document, int errorCode, ErrorSeverity severity)
: this(exception, document, 0, 0, errorCode, severity)
{
}
public SqlServerError(string message, string document, int line, int column, ErrorSeverity severity)
: this(message, null, document, line, column, Constants.UndefinedErrorCode, severity)
{
}
public SqlServerError(
Exception exception,
string document,
int line,
int column,
int errorCode,
ErrorSeverity severity) :
this(exception.Message, exception, document, line, column, errorCode, severity)
{
}
public SqlServerError(
string message,
string document,
int line,
int column,
int errorCode,
ErrorSeverity severity) :
this(message, null, document, line, column, errorCode, severity)
{
}
public SqlServerError(
string message,
Exception exception,
string document,
int line,
int column,
int errorCode,
ErrorSeverity severity) :
base(message, exception, document, line, column, SqlServerPrefix, errorCode, severity)
{
this.HelpKeyword = DefaultHelpKeyword;
}
}
}

View File

@@ -0,0 +1,54 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Globalization;
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
{
/// <summary>
/// Captures extended information about a specific error and a retry
/// </summary>
public class SqlServerRetryError : SqlServerError
{
private int _retryCount;
private int _errorCode;
public SqlServerRetryError(string message, Exception ex, int retryCount, int errorCode, ErrorSeverity severity)
: base(ex, message, errorCode, severity)
{
_retryCount = retryCount;
_errorCode = errorCode;
}
public int RetryCount
{
get { return _retryCount; }
}
public static string FormatRetryMessage(int retryCount, TimeSpan delay, Exception transientException)
{
string message = string.Format(
CultureInfo.CurrentCulture,
Resources.RetryOnException,
retryCount,
delay.TotalMilliseconds.ToString(CultureInfo.CurrentCulture),
transientException.ToString());
return message;
}
public static string FormatIgnoreMessage(int retryCount, Exception exception)
{
string message = string.Format(
CultureInfo.CurrentCulture,
Resources.IgnoreOnException,
retryCount,
exception.ToString());
return message;
}
}
}