mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-30 09:35:38 -05:00
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:
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal enum BatchParserAction
|
||||
{
|
||||
Continue = 0,
|
||||
Abort = 1
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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; } }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,490 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// 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; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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,
|
||||
}
|
||||
}
|
||||
@@ -1,891 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Single batch of SQL command
|
||||
/// </summary>
|
||||
internal 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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
/// Error totalAffectedRows for a Batch
|
||||
/// </summary>
|
||||
internal 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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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; } }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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.");
|
||||
}
|
||||
|
||||
internal void DisableVariableSubstitution()
|
||||
{
|
||||
variableSubstitutionDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for handling SQL CMD by Batch Parser
|
||||
/// </summary>
|
||||
internal 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
|
||||
}
|
||||
|
||||
internal ConnectionChangedDelegate ConnectionChanged
|
||||
{
|
||||
get { return connectionChangedDelegate; }
|
||||
set { connectionChangedDelegate = value; }
|
||||
}
|
||||
|
||||
internal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,277 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal class ScriptExecutionFinishedEventArgs : EventArgs
|
||||
{
|
||||
internal ScriptExecutionFinishedEventArgs(ScriptExecutionResult result)
|
||||
{
|
||||
ExecutionResult = result;
|
||||
}
|
||||
|
||||
public ScriptExecutionResult ExecutionResult
|
||||
{
|
||||
private set;
|
||||
get;
|
||||
}
|
||||
|
||||
public bool Disposing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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]
|
||||
internal enum ScriptExecutionResult
|
||||
{
|
||||
Success = 0x1,
|
||||
Failure = 0x2,
|
||||
Cancel = 0x4,
|
||||
Halted = 0x8,
|
||||
All = 0x0F
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// 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 ScriptMessageType
|
||||
{
|
||||
FatalError,
|
||||
Error,
|
||||
Warning
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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]
|
||||
internal enum ShowPlanType
|
||||
{
|
||||
None = 0x0,
|
||||
ActualExecutionShowPlan = 0x1,
|
||||
ActualXmlShowPlan = 0x2,
|
||||
EstimatedExecutionShowPlan = 0x4,
|
||||
EstimatedXmlShowPlan = 0x8,
|
||||
AllXmlShowPlan = ActualXmlShowPlan | EstimatedXmlShowPlan,
|
||||
AllExecutionShowPlan = ActualExecutionShowPlan | EstimatedExecutionShowPlan,
|
||||
AllShowPlan = AllExecutionShowPlan | AllXmlShowPlan
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
//
|
||||
// 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 struct TextSpan
|
||||
{
|
||||
public int iEndIndex;
|
||||
public int iEndLine;
|
||||
public int iStartIndex;
|
||||
public int iStartLine;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal interface ICommandHandler
|
||||
{
|
||||
BatchParserAction Go(TextBlock batch, int repeatCount);
|
||||
BatchParserAction OnError(Token token, OnErrorAction action);
|
||||
BatchParserAction Include(TextBlock filename, out TextReader stream, out string newFilename);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal interface IVariableResolver
|
||||
{
|
||||
string GetVariable(PositionStruct pos, string name);
|
||||
void SetVariable(PositionStruct pos, string name, string value);
|
||||
}
|
||||
}
|
||||
@@ -1,747 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Lexer class for the SMO Batch Parser
|
||||
/// </summary>
|
||||
internal 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; }
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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,
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal enum OnErrorAction
|
||||
{
|
||||
Ignore = 0,
|
||||
Exit = 1,
|
||||
}
|
||||
}
|
||||
@@ -1,722 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// The Parser class on which the Batch Parser is based on
|
||||
/// </summary>
|
||||
internal 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;
|
||||
}
|
||||
|
||||
internal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
//
|
||||
// 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]
|
||||
internal 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; } }
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
internal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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; }
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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; }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||
@@ -73,11 +72,4 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||
/// </summary>
|
||||
public Dictionary<string, object> Options { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class ClusterEndpoint
|
||||
{
|
||||
public string ServiceName;
|
||||
public string IpAddress;
|
||||
public int Port;
|
||||
}
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal sealed class AmbientSettings
|
||||
{
|
||||
private const string LogicalContextName = "__LocalContextConfigurationName";
|
||||
|
||||
internal 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";
|
||||
|
||||
internal 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; }
|
||||
}
|
||||
|
||||
internal bool SupportAlwaysEncrypted
|
||||
{
|
||||
get { return (bool)_configuration[SupportAlwaysEncryptedIndex].Value; }
|
||||
set { _configuration[SupportAlwaysEncryptedIndex].Value = value; }
|
||||
}
|
||||
|
||||
internal bool AlwaysEncryptedWizardMigration
|
||||
{
|
||||
get { return (bool)_configuration[AlwaysEncryptedWizardMigrationIndex].Value; }
|
||||
set { _configuration[AlwaysEncryptedWizardMigrationIndex].Value = value; }
|
||||
}
|
||||
|
||||
internal bool SkipObjectTypeBlocking
|
||||
{
|
||||
get { return (bool)_configuration[SkipObjectTypeBlockingIndex].Value; }
|
||||
set { _configuration[SkipObjectTypeBlockingIndex].Value = value; }
|
||||
}
|
||||
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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>
|
||||
internal 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);
|
||||
}
|
||||
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
//
|
||||
// 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)";
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
//
|
||||
// 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]
|
||||
internal 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); }
|
||||
}
|
||||
|
||||
internal 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>
|
||||
internal bool IsOnDisplay { get; set; }
|
||||
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal enum ErrorSeverity
|
||||
{
|
||||
Unknown = 0,
|
||||
Error,
|
||||
Warning,
|
||||
Message
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal interface IStackSettingsContext : IDisposable
|
||||
{
|
||||
AmbientSettings.AmbientData Settings { get; }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,247 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal sealed partial class ReliableSqlConnection
|
||||
{
|
||||
internal 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();
|
||||
}
|
||||
|
||||
internal SqlCommand GetUnderlyingCommand()
|
||||
{
|
||||
return _command;
|
||||
}
|
||||
|
||||
internal void ValidateConnectionIsSet()
|
||||
{
|
||||
if (_connection == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.ConnectionPropertyNotSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,618 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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.
|
||||
internal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal static class Resources
|
||||
{
|
||||
internal static string AmbientSettingFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
return "{0}: {1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ConnectionPassedToIsCloudShouldBeOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return "connection passed to IsCloud should be open.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ConnectionPropertyNotSet
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Connection property has not been initialized.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ExceptionCannotBeRetried
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Exception cannot be retried because of err #{0}:{1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ErrorParsingConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Error parsing connection string {0}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string FailedToCacheIsCloud
|
||||
{
|
||||
get
|
||||
{
|
||||
return "failed to cache the server property of IsAzure";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string FailedToParseConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return "failed to parse the provided connection string: {0}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string IgnoreOnException
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Retry number {0}. Ignoring Exception: {1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string InvalidCommandType
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Unsupported command object. Use SqlCommand or ReliableSqlCommand.";
|
||||
}
|
||||
}
|
||||
|
||||
internal 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";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string OnlyReliableConnectionSupported
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Connection property can only be set to a value of type ReliableSqlConnection.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string RetryOnException
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Retry number {0}. Delaying {1} ms before next retry. Exception: {2}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ThrottlingTypeInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return "ThrottlingTypeInfo";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string UnableToAssignValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Unable to assign the value of type {0} to {1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
//
|
||||
// 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]
|
||||
internal sealed class RetryLimitExceededException : Exception
|
||||
{
|
||||
internal RetryLimitExceededException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
internal RetryLimitExceededException(string m, Exception e) : base(m, e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the error detection logic for temporary faults that are commonly found during data transfer.
|
||||
/// </summary>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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>
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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>
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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 balancer’s 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
|
||||
}
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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;
|
||||
}
|
||||
*/
|
||||
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
|
||||
internal 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 };
|
||||
}
|
||||
|
||||
internal sealed class RetryStateEx : RetryState
|
||||
{
|
||||
public TimeSpan TotalRetryTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,459 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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;
|
||||
}
|
||||
|
||||
internal 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);
|
||||
}
|
||||
|
||||
internal 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);
|
||||
}
|
||||
|
||||
internal 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);
|
||||
}
|
||||
|
||||
internal static void CommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Command");
|
||||
}
|
||||
|
||||
internal static void CommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Command");
|
||||
}
|
||||
|
||||
internal static void CreateDatabaseCommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Database Command");
|
||||
}
|
||||
|
||||
internal static void CreateDatabaseCommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Database Command");
|
||||
}
|
||||
|
||||
internal static void ElementCommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Element Command");
|
||||
}
|
||||
|
||||
internal static void ElementCommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Element Command");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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>
|
||||
internal 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>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
|
||||
internal 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));
|
||||
}
|
||||
}
|
||||
|
||||
internal 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>
|
||||
internal 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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// 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];";
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,465 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
internal 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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
//
|
||||
// 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]
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// 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>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -249,64 +249,14 @@ EditDataIncorrectTable(string tableName) = EditData queries must query the origi
|
||||
############################################################################
|
||||
# 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_ExecutionInfo_InitializingLoop = Beginning execution loop
|
||||
|
||||
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}
|
||||
EE_ExecutionInfo_FinalizingLoop = Batch execution completed {0} times...
|
||||
|
||||
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.
|
||||
|
||||
############################################################################
|
||||
# Workspace Service
|
||||
|
||||
@@ -716,125 +666,6 @@ StoredProcedureScriptParameterComment = -- TODO: Set parameter values here.
|
||||
ScriptingGeneralError = An error occurred while scripting the objects.
|
||||
ScriptingExecuteNotSupportedError = Scripting as Execute is only supported for Stored Procedures
|
||||
|
||||
############################################################################
|
||||
# Admin Service
|
||||
|
||||
unavailable = Unavailable
|
||||
filegroup_dialog_defaultFilegroup = Current default filegroup: {0}
|
||||
filegroup_dialog_title = New Filegroup for {0}
|
||||
filegroups_default = Default
|
||||
filegroups_files = Files
|
||||
filegroups_name = Name
|
||||
filegroups_readonly = Read-Only
|
||||
general_autogrowth = Autogrowth / Maxsize
|
||||
general_builderText = ...
|
||||
general_default = <default>
|
||||
general_fileGroup = Filegroup
|
||||
general_fileName = Logical Name
|
||||
general_fileType = File Type
|
||||
general_initialSize = Initial Size (MB)
|
||||
general_newFilegroup = <new filegroup>
|
||||
general_path = Path
|
||||
general_physicalFileName = File Name
|
||||
general_rawDevice = <raw device>
|
||||
general_recoveryModel_bulkLogged = Bulk-logged
|
||||
general_recoveryModel_full = Full
|
||||
general_recoveryModel_simple = Simple
|
||||
general_titleSearchOwner = Select Database Owner
|
||||
|
||||
prototype_autogrowth_disabled = None
|
||||
prototype_autogrowth_restrictedGrowthByMB = By {0} MB, Limited to {1} MB
|
||||
prototype_autogrowth_restrictedGrowthByPercent = By {0} percent, Limited to {1} MB
|
||||
prototype_autogrowth_unrestrictedGrowthByMB = By {0} MB, Unlimited
|
||||
prototype_autogrowth_unrestrictedGrowthByPercent = By {0} percent, Unlimited
|
||||
prototype_autogrowth_unlimitedfilestream = Unlimited
|
||||
prototype_autogrowth_limitedfilestream = Limited to {0} MB
|
||||
prototype_db_category_automatic = Automatic
|
||||
prototype_db_category_servicebroker = Service Broker
|
||||
prototype_db_category_collation = Collation
|
||||
prototype_db_category_cursor = Cursor
|
||||
prototype_db_category_misc = Miscellaneous
|
||||
prototype_db_category_recovery = Recovery
|
||||
prototype_db_category_state = State
|
||||
prototype_db_prop_ansiNullDefault = ANSI NULL Default
|
||||
prototype_db_prop_ansiNulls = ANSI NULLS Enabled
|
||||
prototype_db_prop_ansiPadding = ANSI Padding Enabled
|
||||
prototype_db_prop_ansiWarnings = ANSI Warnings Enabled
|
||||
prototype_db_prop_arithabort = Arithmetic Abort Enabled
|
||||
prototype_db_prop_autoClose = Auto Close
|
||||
prototype_db_prop_autoCreateStatistics = Auto Create Statistics
|
||||
prototype_db_prop_autoShrink = Auto Shrink
|
||||
prototype_db_prop_autoUpdateStatistics = Auto Update Statistics
|
||||
prototype_db_prop_autoUpdateStatisticsAsync = Auto Update Statistics Asynchronously
|
||||
prototype_db_prop_caseSensitive = Case Sensitive
|
||||
prototype_db_prop_closeCursorOnCommit = Close Cursor on Commit Enabled
|
||||
prototype_db_prop_collation = Collation
|
||||
prototype_db_prop_concatNullYieldsNull = Concatenate Null Yields Null
|
||||
prototype_db_prop_databaseCompatibilityLevel = Database Compatibility Level
|
||||
prototype_db_prop_databaseState = Database State
|
||||
prototype_db_prop_defaultCursor = Default Cursor
|
||||
prototype_db_prop_fullTextIndexing = Full-Text Indexing Enabled
|
||||
prototype_db_prop_numericRoundAbort = Numeric Round-Abort
|
||||
prototype_db_prop_pageVerify = Page Verify
|
||||
prototype_db_prop_quotedIdentifier = Quoted Identifiers Enabled
|
||||
prototype_db_prop_readOnly = Database Read-Only
|
||||
prototype_db_prop_recursiveTriggers = Recursive Triggers Enabled
|
||||
prototype_db_prop_restrictAccess = Restrict Access
|
||||
prototype_db_prop_selectIntoBulkCopy = Select Into/Bulk Copy
|
||||
prototype_db_prop_honorBrokerPriority = Honor Broker Priority
|
||||
prototype_db_prop_serviceBrokerGuid = Service Broker Identifier
|
||||
prototype_db_prop_brokerEnabled = Broker Enabled
|
||||
prototype_db_prop_truncateLogOnCheckpoint = Truncate Log on Checkpoint
|
||||
prototype_db_prop_dbChaining = Cross-database Ownership Chaining Enabled
|
||||
prototype_db_prop_trustworthy = Trustworthy
|
||||
prototype_db_prop_dateCorrelationOptimization = Date Correlation Optimization Enabled
|
||||
prototype_db_prop_parameterization = Parameterization
|
||||
prototype_db_prop_parameterization_value_forced = Forced
|
||||
prototype_db_prop_parameterization_value_simple = Simple
|
||||
prototype_file_dataFile = ROWS Data
|
||||
prototype_file_logFile = LOG
|
||||
prototype_file_filestreamFile = FILESTREAM Data
|
||||
prototype_file_noFileGroup = Not Applicable
|
||||
prototype_file_defaultpathstring = <default path>
|
||||
title_openConnectionsMustBeClosed = Open Connections
|
||||
warning_openConnectionsMustBeClosed=To change the database properties, SQL Server must close all other connections to the database_ Are you sure you want to change the properties and close all other connections?
|
||||
prototype_db_prop_databaseState_value_autoClosed = AUTO_CLOSED
|
||||
prototype_db_prop_databaseState_value_emergency = EMERGENCY
|
||||
prototype_db_prop_databaseState_value_inaccessible = INACCESSIBLE
|
||||
prototype_db_prop_databaseState_value_normal = NORMAL
|
||||
prototype_db_prop_databaseState_value_offline = OFFLINE
|
||||
prototype_db_prop_databaseState_value_recovering = RECOVERING
|
||||
prototype_db_prop_databaseState_value_recoveryPending = RECOVERY PENDING
|
||||
prototype_db_prop_databaseState_value_restoring = RESTORING
|
||||
prototype_db_prop_databaseState_value_shutdown=SHUTDOWN
|
||||
prototype_db_prop_databaseState_value_standby = STANDBY
|
||||
prototype_db_prop_databaseState_value_suspect = SUSPECT
|
||||
prototype_db_prop_defaultCursor_value_global = GLOBAL
|
||||
prototype_db_prop_defaultCursor_value_local = LOCAL
|
||||
prototype_db_prop_restrictAccess_value_multiple = MULTI_USER
|
||||
prototype_db_prop_restrictAccess_value_restricted = RESTRICTED_USER
|
||||
prototype_db_prop_restrictAccess_value_single = SINGLE_USER
|
||||
prototype_db_prop_pageVerify_value_checksum = CHECKSUM
|
||||
prototype_db_prop_pageVerify_value_none = NONE
|
||||
prototype_db_prop_pageVerify_value_tornPageDetection = TORN_PAGE_DETECTION
|
||||
prototype_db_prop_varDecimalEnabled = VarDecimal Storage Format Enabled
|
||||
compatibilityLevel_katmai = SQL Server 2008 (100)
|
||||
prototype_db_prop_encryptionEnabled = Encryption Enabled
|
||||
prototype_db_prop_databasescopedconfig_value_off = OFF
|
||||
prototype_db_prop_databasescopedconfig_value_on = ON
|
||||
prototype_db_prop_databasescopedconfig_value_primary = PRIMARY
|
||||
error_db_prop_invalidleadingColumns = For the distribution policy HASH, the number of leading hash columns is optional but should be from 1 to 16 columns
|
||||
compatibilityLevel_denali = SQL Server 2012 (110)
|
||||
compatibilityLevel_sql14 = SQL Server 2014 (120)
|
||||
compatibilityLevel_sql15 = SQL Server 2016 (130)
|
||||
compatibilityLevel_sqlvNext = SQL Server vNext (140)
|
||||
general_containmentType_None = None
|
||||
general_containmentType_Partial = Partial
|
||||
filegroups_filestreamFiles = FILESTREAM Files
|
||||
prototype_file_noApplicableFileGroup = No Applicable Filegroup
|
||||
NeverBackedUp = Never
|
||||
Error_InvalidDirectoryName = Path {0} is not a valid directory
|
||||
Error_ExistingDirectoryName = For directory {0} a file with name {1} already exists
|
||||
|
||||
############################################################################
|
||||
# Backup Service
|
||||
@@ -895,146 +726,6 @@ StopSessionFailed(String error) = Failed to stop session: {0}
|
||||
SessionNotFound = Cannot find requested XEvent session
|
||||
SessionAlreadyExists(String sessionName) = An XEvent session named {0} already exists
|
||||
|
||||
#############################################################################
|
||||
# SQL Agent
|
||||
EnableAlertsTitle(String serverName) = Enable Alerts - {0}
|
||||
EnableAlertDescription(String alertName) = Enable Alert '{0}'
|
||||
EnablingAlert(String alertName) = Enabling Alert '{0}'
|
||||
EnabledAlert(String alertName) = Enabled Alert '{0}'
|
||||
|
||||
DisableAlertsTitle(String serverName) = Disable Alerts - {0}
|
||||
DisableAlertDescription(String alertName) = Disable Alert '{0}'
|
||||
DisablingAlert(String alertName) = Disabling Alert '{0}'
|
||||
DisabledAlert(String alertName) = Disabled Alert '{0}'
|
||||
|
||||
EnableJobsTitle(String serverName) = Enable Jobs - {0}
|
||||
EnableJobDescription(String jobName) = Enable Job '{0}'
|
||||
EnablingJob(String jobName) = Enabling Job '{0}'
|
||||
EnabledJob(String jobName) = Enabled Job '{0}'
|
||||
|
||||
DisableJobsTitle(String serverName) = Disable Jobs - {0}
|
||||
DisableJobDescription(String jobName) = Disable Job '{0}'
|
||||
DisablingJob(String jobName) = Disabling Job '{0}'
|
||||
DisabledJob(String jobName) = Disabled Job '{0}'
|
||||
|
||||
StartJobsTitle(String serverName) = Start Jobs - {0}
|
||||
StartJobDescription(String jobName) = Start Job '{0}'
|
||||
GettingStartStep(String jobName) = Job '{0}' has more than one step. Getting step to start
|
||||
UserCancelledSelectStep = User canceled select step. Job will not be started
|
||||
StartingJob(String jobName) = Starting Job '{0}'
|
||||
StartJobWithStep(String jobName, String stepName) = Start Job '{0}' with step '{1}'
|
||||
RequestPostedToTargetServers = Posted remote job execution request
|
||||
|
||||
ExecuteJob(string jobName) = Execute job '{0}'
|
||||
JobFailed(string jobName) = Execution of job '{0}' failed. See the history log for details.
|
||||
Executing = Executing
|
||||
BetweenRetries = Between retries
|
||||
Suspended = Suspended
|
||||
PerformingCompletionAction = Performing completion action
|
||||
WaitingForStepToFinish = Waiting for step to finish
|
||||
WaitingForWorkerThread = Waiting for worker thread
|
||||
|
||||
StopJobsTitle(String serverName) = Stop Jobs - {0}
|
||||
StopJobDescription(String jobName) = Stop Job '{0}'
|
||||
StoppingJob(String jobName) = Stopping Job '{0}'
|
||||
StoppedJob(String jobName) = Stopped Job '{0}'
|
||||
|
||||
; First item in database name drop down list
|
||||
AllDatabases = <all databases>
|
||||
; Exception thrown when agent alert has unknown severity level
|
||||
UnknownSeverity(int severity) = Unknown severity: {0}
|
||||
|
||||
#severity types
|
||||
Severity001 = 001 - Miscellaneous System Information
|
||||
Severity002 = 002 - Reserved
|
||||
Severity003 = 003 - Reserved
|
||||
Severity004 = 004 - Reserved
|
||||
Severity005 = 005 - Reserved
|
||||
Severity006 = 006 - Reserved
|
||||
Severity007 = 007 - Notification: Status Information
|
||||
Severity008 = 008 - Notification: User Intervention Required
|
||||
Severity009 = 009 - User Defined
|
||||
Severity010 = 010 - Information
|
||||
Severity011 = 011 - Specified Database Object Not Found
|
||||
Severity012 = 012 - Unused
|
||||
Severity013 = 013 - User Transaction Syntax Error
|
||||
Severity014 = 014 - Insufficient Permission
|
||||
Severity015 = 015 - Syntax Error in SQL Statements
|
||||
Severity016 = 016 - Miscellaneous User Error
|
||||
Severity017 = 017 - Insufficient Resources
|
||||
Severity018 = 018 - Nonfatal Internal Error
|
||||
Severity019 = 019 - Fatal Error in Resource
|
||||
Severity020 = 020 - Fatal Error in Current Process
|
||||
Severity021 = 021 - Fatal Error in Database Processes
|
||||
Severity022 = 022 - Fatal Error: Table Integrity Suspect
|
||||
Severity023 = 023 - Fatal Error: Database Integrity Suspect
|
||||
Severity024 = 024 - Fatal Error: Hardware Error
|
||||
Severity025 = 025 - Fatal Error
|
||||
|
||||
; Message box that displayed if start time is more than end time on Mon-Fri
|
||||
PagerScheduleMonFri = Pager schedule end time is earlier than start time on Mon-Fri. Do you want to continue?
|
||||
; Message box that displayed if start time is more than end time on Sat-Sun
|
||||
PagerScheduleSatSun = Pager schedule end time is earlier than start time on Sat-Sun. Do you want to continue?
|
||||
; Message box caption
|
||||
PagerScheduleWarning = Pager schedule warning
|
||||
|
||||
; Tree node name
|
||||
General = General
|
||||
; Tree node name
|
||||
Notifications = Notifications
|
||||
; Tree node name
|
||||
History = History
|
||||
; Pager schedule grid column name
|
||||
Day = Day
|
||||
; Pager schedule grid column name
|
||||
StartTime = Start Time
|
||||
; Pager schedule grid column name
|
||||
EndTime = End Time
|
||||
; Exception thrown when column index is invalid
|
||||
ColumnIndexIsInvalid = Column index is invalid.
|
||||
; Exception thrown when row index is invalid
|
||||
RowIndexIsInvalid = Row index is invalid.
|
||||
; Name of the operator dialog in create new operator mode
|
||||
NewOperatorProperties = New Operator
|
||||
; Name of the operator dialog in modify operator mode
|
||||
OperatorProperties(string operatorName) = {0} Properties
|
||||
; Exception thrown when dialog cannot be created/intialized.
|
||||
FailedToCreateInitializeAgentOperatorDialog = Unable to create/initialize Agent Operator dialog.
|
||||
; Exception thrown when job server is not available
|
||||
JobServerIsNotAvailable = Job server is not available.
|
||||
; Exception thrown when we cannot create/initialize agent operators general page
|
||||
CannotCreateInitializeGeneralPage = Cannot create/initialize General page.
|
||||
; Exception thrown when we cannot create/initialize agent operators notifications page
|
||||
CannotCreateInitializeNotificationsPage = Cannot create/initialize Notifications page.
|
||||
; Exception thrown when we cannot create/initialize agent operators history page
|
||||
CannotCreateInitializeHistoryPage = Cannot create/initialize History page.
|
||||
; Exception throw when dialog cannot refresh operator
|
||||
CannotResetOperator = Cannot reset operator.
|
||||
|
||||
; Name of label on notifications page
|
||||
AlertList = Alert list:
|
||||
; Name of label on notifications page
|
||||
JobList = Job list:
|
||||
; Name of column on notifications page
|
||||
Email = E-mail
|
||||
; Name of column on notifications page
|
||||
Pager = Pager
|
||||
; Name of column on notifications page
|
||||
AlertName = Alert name
|
||||
; Name of column on notifications page
|
||||
JobName = Job name
|
||||
; Completion Action
|
||||
Always = Always
|
||||
; Completion Action
|
||||
Never = Never
|
||||
; Completion Action
|
||||
OnFailure = On failure
|
||||
; Completion Action
|
||||
OnSuccess = On success
|
||||
; Exception thrown when we cannot modify alerts
|
||||
CannotModifyAlerts = Cannot modify alerts.
|
||||
; Exception thrown when we cannot create script that modify alerts
|
||||
CannotCreateScriptForModifyAlerts = Cannot create script for modify alerts.
|
||||
|
||||
;job categories
|
||||
CategoryLocal = [Uncategorized (Local)]
|
||||
@@ -1081,4 +772,14 @@ StartDateGreaterThanEndDate = The job schedule starting date cannot be greater t
|
||||
StartTimeGreaterThanEndTime = The job schedule starting time cannot be after the ending time.
|
||||
EndTimeEqualToStartTime = The job schedule ending time must be after the starting time.
|
||||
InvalidStartDate = Start date must be on or after January 1, 1990.
|
||||
ScheduleNameAlreadyExists(string scheduleName)=There is already a schedule named '{0}' for this job. You must specify a different name.
|
||||
ScheduleNameAlreadyExists(string scheduleName)=There is already a schedule named '{0}' for this job. You must specify a different name.
|
||||
|
||||
; Exception thrown when job server is not available
|
||||
JobServerIsNotAvailable = Job server is not available
|
||||
|
||||
############################################################################
|
||||
# Admin Service
|
||||
|
||||
NeverBackedUp = Never
|
||||
Error_InvalidDirectoryName = Path {0} is not a valid directory
|
||||
Error_ExistingDirectoryName = For directory {0} a file with name {1} already exist
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" Version="$(SmoPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.SqlServer.DacFx" Version="150.4240.1-preview" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0-preview3-26501-04" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="**\*.cs" />
|
||||
@@ -32,18 +32,11 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Microsoft.SqlTools.Hosting/Microsoft.SqlTools.Hosting.csproj" />
|
||||
<ProjectReference Include="../Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj" />
|
||||
<ProjectReference Include="../Microsoft.SqlTools.ManagedBatchParser/Microsoft.SqlTools.ManagedBatchParser.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ObjectExplorer\SmoModel\TreeNodeDefinition.xml" />
|
||||
<EmbeddedResource Include="Localization\sr.resx" />
|
||||
<None Include="Localization\sr.strings" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Connection\ReliableConnection\ReliableSqlCommand.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Connection\ReliableConnection\ReliableSqlConnection.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -43,6 +43,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.UnitTests")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.IntegrationTests")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ManagedBatchParser.UnitTests")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.Test.Common")]
|
||||
|
||||
// Allowing internals visible access to Moq library to help testing
|
||||
|
||||
Reference in New Issue
Block a user