mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-26 01:25:42 -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:
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public enum BatchParserAction
|
||||
{
|
||||
Continue = 0,
|
||||
Abort = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public sealed class BatchParserException : Exception
|
||||
{
|
||||
const string ErrorCodeName = "ErrorCode";
|
||||
const string BeginName = "Begin";
|
||||
const string EndName = "End";
|
||||
const string TextName = "Text";
|
||||
const string TokenTypeName = "TokenType";
|
||||
|
||||
ErrorCode errorCode;
|
||||
PositionStruct begin;
|
||||
PositionStruct end;
|
||||
string text;
|
||||
LexerTokenType tokenType;
|
||||
|
||||
/// <summary>
|
||||
/// Class for a custom exception for the Batch Parser
|
||||
/// </summary>
|
||||
public BatchParserException(ErrorCode errorCode, Token token, string message)
|
||||
: base(message)
|
||||
{
|
||||
this.errorCode = errorCode;
|
||||
begin = token.Begin;
|
||||
end = token.End;
|
||||
text = token.Text;
|
||||
tokenType = token.TokenType;
|
||||
}
|
||||
|
||||
public ErrorCode ErrorCode { get { return errorCode; } }
|
||||
|
||||
public PositionStruct Begin { get { return begin; } }
|
||||
|
||||
public PositionStruct End { get { return end; } }
|
||||
|
||||
public string Text { get { return text; } }
|
||||
|
||||
public LexerTokenType TokenType { get { return tokenType; } }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.SqlTools.ManagedBatchParser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the SMO Batch parser to make it a easily useable component.
|
||||
/// </summary>
|
||||
public sealed class BatchParserWrapper : IDisposable
|
||||
{
|
||||
private List<BatchInfo> batchInfos;
|
||||
private ExecutionEngine executionEngine;
|
||||
private BatchEventNotificationHandler notificationHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Helper method used to Convert line/column information in a file to offset
|
||||
/// </summary>
|
||||
private static List<BatchDefinition> ConvertToBatchDefinitionList(string content, List<BatchInfo> batchInfos)
|
||||
{
|
||||
|
||||
List<BatchDefinition> batchDefinitionList = new List<BatchDefinition>();
|
||||
if (batchInfos.Count == 0)
|
||||
{
|
||||
return batchDefinitionList;
|
||||
}
|
||||
List<int> offsets = GetOffsets(content, batchInfos);
|
||||
|
||||
if (!string.IsNullOrEmpty(content) && (batchInfos.Count > 0))
|
||||
{
|
||||
// Instantiate a string reader for the whole sql content
|
||||
using (StringReader reader = new StringReader(content))
|
||||
{
|
||||
|
||||
// Generate the first batch definition list
|
||||
int startLine = batchInfos[0].startLine + 1; //positions is 0 index based
|
||||
int endLine = startLine;
|
||||
int lineDifference = 0;
|
||||
int endColumn;
|
||||
int offset = offsets[0];
|
||||
int startColumn = batchInfos[0].startColumn;
|
||||
int count = batchInfos.Count;
|
||||
string batchText = content.Substring(offset, batchInfos[0].length);
|
||||
|
||||
// if there's only one batch then the line difference is just startLine
|
||||
if (count > 1)
|
||||
{
|
||||
lineDifference = batchInfos[1].startLine - batchInfos[0].startLine;
|
||||
}
|
||||
|
||||
// get endLine, endColumn for the current batch and the lineStartOffset for the next batch
|
||||
var position = ReadLines(reader, lineDifference, endLine);
|
||||
endLine = position.Item1;
|
||||
endColumn = position.Item2;
|
||||
|
||||
// create a new BatchDefinition and add it to the list
|
||||
BatchDefinition batchDef = new BatchDefinition(
|
||||
batchText,
|
||||
startLine,
|
||||
endLine,
|
||||
startColumn + 1,
|
||||
endColumn + 1,
|
||||
batchInfos[0].executionCount
|
||||
);
|
||||
|
||||
batchDefinitionList.Add(batchDef);
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
offset = offsets[1] + batchInfos[0].startColumn;
|
||||
}
|
||||
|
||||
// Generate the rest batch definitions
|
||||
for (int index = 1; index < count - 1; index++)
|
||||
{
|
||||
lineDifference = batchInfos[index + 1].startLine - batchInfos[index].startLine;
|
||||
position = ReadLines(reader, lineDifference, endLine);
|
||||
endLine = position.Item1;
|
||||
endColumn = position.Item2;
|
||||
offset = offsets[index];
|
||||
batchText = content.Substring(offset, batchInfos[index].length);
|
||||
startLine = batchInfos[index].startLine;
|
||||
startColumn = batchInfos[index].startColumn;
|
||||
|
||||
// make a new batch definition for each batch
|
||||
BatchDefinition batch = new BatchDefinition(
|
||||
batchText,
|
||||
startLine,
|
||||
endLine,
|
||||
startColumn + 1,
|
||||
endColumn + 1,
|
||||
batchInfos[index].executionCount
|
||||
);
|
||||
batchDefinitionList.Add(batch);
|
||||
}
|
||||
|
||||
// if there is only one batch then that was the last one anyway
|
||||
if (count > 1)
|
||||
{
|
||||
|
||||
batchText = content.Substring(offsets[count-1], batchInfos[count - 1].length);
|
||||
BatchDefinition lastBatchDef = GetLastBatchDefinition(reader, batchInfos[count - 1], batchText);
|
||||
batchDefinitionList.Add(lastBatchDef);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return batchDefinitionList;
|
||||
}
|
||||
|
||||
private static int GetMaxStartLine(IList<BatchInfo> batchInfos)
|
||||
{
|
||||
int highest = 0;
|
||||
foreach (var batchInfo in batchInfos)
|
||||
{
|
||||
if (batchInfo.startLine > highest)
|
||||
{
|
||||
highest = batchInfo.startLine;
|
||||
}
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets offsets for all batches
|
||||
/// </summary>
|
||||
private static List<int> GetOffsets(string content, IList<BatchInfo> batchInfos)
|
||||
{
|
||||
|
||||
List<int> offsets = new List<int>();
|
||||
int count = 0;
|
||||
int offset = 0;
|
||||
bool foundAllOffsets = false;
|
||||
int maxStartLine = GetMaxStartLine(batchInfos);
|
||||
using (StringReader reader = new StringReader(content))
|
||||
{
|
||||
// go until we have found offsets for all batches
|
||||
while (!foundAllOffsets)
|
||||
{
|
||||
// go until the last start line of the batches
|
||||
for (int i = 0; i <= maxStartLine ; i++)
|
||||
{
|
||||
// get offset for the current batch
|
||||
ReadLines(reader, ref count, ref offset, ref foundAllOffsets, batchInfos, offsets, i);
|
||||
|
||||
// if we found all the offsets, then we're done
|
||||
if (foundAllOffsets)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to read lines of batches to get offsets
|
||||
/// </summary>
|
||||
private static void ReadLines(StringReader reader, ref int count, ref int offset, ref bool foundAllOffsets,
|
||||
IList<BatchInfo> batchInfos, List<int> offsets, int iteration)
|
||||
{
|
||||
int ch;
|
||||
while (true)
|
||||
{
|
||||
if (batchInfos[count].startLine == iteration)
|
||||
{
|
||||
count++;
|
||||
offsets.Add(offset);
|
||||
if (count == batchInfos.Count)
|
||||
{
|
||||
foundAllOffsets = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ch = reader.Read();
|
||||
if (ch == -1) // EOF do nothing
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (ch == 10 /* for \n */) // End of line increase and break
|
||||
{
|
||||
offset++;
|
||||
break;
|
||||
}
|
||||
else // regular char just increase
|
||||
{
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to get the last batch
|
||||
/// </summary>
|
||||
private static BatchDefinition GetLastBatchDefinition(StringReader reader,
|
||||
BatchInfo batchInfo, string batchText)
|
||||
{
|
||||
int startLine = batchInfo.startLine;
|
||||
int startColumn = batchInfo.startColumn;
|
||||
string prevLine = null;
|
||||
string line = reader.ReadLine();
|
||||
int endLine = startLine;
|
||||
|
||||
// find end line
|
||||
while (line != null)
|
||||
{
|
||||
endLine++;
|
||||
if (line != "\n")
|
||||
{
|
||||
prevLine = line;
|
||||
}
|
||||
line = reader.ReadLine();
|
||||
}
|
||||
|
||||
// get number of characters in the last line
|
||||
int endColumn = prevLine.ToCharArray().Length;
|
||||
|
||||
return new BatchDefinition(
|
||||
batchText,
|
||||
startLine,
|
||||
endLine,
|
||||
startColumn + 1,
|
||||
endColumn + 1,
|
||||
batchInfo.executionCount
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to get correct lines and columns
|
||||
/// in a single batch with multiple statements
|
||||
/// </summary>
|
||||
private static Tuple<int, int> GetBatchPositionDetails(StringReader reader, int endLine)
|
||||
{
|
||||
string prevLine = null;
|
||||
string line = reader.ReadLine();
|
||||
|
||||
// find end line
|
||||
while (line != null)
|
||||
{
|
||||
endLine++;
|
||||
if (line != "\n")
|
||||
{
|
||||
prevLine = line;
|
||||
}
|
||||
line = reader.ReadLine();
|
||||
}
|
||||
|
||||
// get number of characters in the last line
|
||||
int endColumn = prevLine.ToCharArray().Length;
|
||||
|
||||
//lineOffset doesn't matter because its the last batch
|
||||
return Tuple.Create(endLine, endColumn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get end line and end column
|
||||
/// </summary>
|
||||
private static Tuple<int, int> ReadLines(StringReader reader, int n, int endLine)
|
||||
{
|
||||
Validate.IsNotNull(nameof(reader), reader);
|
||||
int endColumn = 0;
|
||||
|
||||
// if only one batch with multiple lines
|
||||
if (n == 0)
|
||||
{
|
||||
return GetBatchPositionDetails(reader, endLine);
|
||||
}
|
||||
|
||||
// if there are more than one batch
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int ch;
|
||||
while (true)
|
||||
{
|
||||
ch = reader.Read();
|
||||
if (ch == -1) // EOF do nothing
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (ch == 10 /* for \n */) // End of line increase and break
|
||||
{
|
||||
++endLine;
|
||||
endColumn = 0;
|
||||
break;
|
||||
}
|
||||
else // regular char just increase
|
||||
{
|
||||
++endColumn;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Tuple.Create(endLine, endColumn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper API for the Batch Parser that returns a list of
|
||||
/// BatchDefinitions when given a string to parse
|
||||
/// </summary>
|
||||
public BatchParserWrapper()
|
||||
{
|
||||
executionEngine = new ExecutionEngine();
|
||||
|
||||
// subscribe to executionEngine BatchParser events
|
||||
executionEngine.BatchParserExecutionError += OnBatchParserExecutionError;
|
||||
|
||||
executionEngine.BatchParserExecutionFinished += OnBatchParserExecutionFinished;
|
||||
|
||||
// instantiate notificationHandler class
|
||||
notificationHandler = new BatchEventNotificationHandler();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes in a query string and returns a list of BatchDefinitions
|
||||
/// </summary>
|
||||
public List<BatchDefinition> GetBatches(string sqlScript)
|
||||
{
|
||||
batchInfos = new List<BatchInfo>();
|
||||
|
||||
// execute the script - all communication / integration after here happen via event handlers
|
||||
executionEngine.ParseScript(sqlScript, notificationHandler);
|
||||
|
||||
// retrieve a list of BatchDefinitions
|
||||
List<BatchDefinition> batchDefinitionList = ConvertToBatchDefinitionList(sqlScript, batchInfos);
|
||||
|
||||
return batchDefinitionList;
|
||||
}
|
||||
|
||||
|
||||
#region ExecutionEngine Event Handlers
|
||||
|
||||
private void OnBatchParserExecutionError(object sender, BatchParserExecutionErrorEventArgs args)
|
||||
{
|
||||
if (args != null)
|
||||
{
|
||||
|
||||
Logger.Write(TraceEventType.Verbose, SR.BatchParserWrapperExecutionError);
|
||||
throw new Exception(string.Format(CultureInfo.CurrentCulture,
|
||||
SR.BatchParserWrapperExecutionEngineError, args.Message + Environment.NewLine + '\t' + args.Description));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBatchParserExecutionFinished(object sender, BatchParserExecutionFinishedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (args != null && args.Batch != null)
|
||||
{
|
||||
// PS168371
|
||||
//
|
||||
// There is a bug in the batch parser where it appends a '\n' to the end of the last
|
||||
// batch if a GO or statement appears at the end of the string without a \r\n. This is
|
||||
// throwing off length calculations in other places in the code because the length of
|
||||
// the string returned is longer than the length of the actual string
|
||||
//
|
||||
// To work around this issue we detect this case (a single \n without a preceding \r
|
||||
// and then adjust the length accordingly
|
||||
string batchText = args.Batch.Text;
|
||||
int batchTextLength = batchText.Length;
|
||||
|
||||
if (!batchText.EndsWith(Environment.NewLine, StringComparison.Ordinal)
|
||||
&& batchText.EndsWith("\n", StringComparison.Ordinal))
|
||||
{
|
||||
batchTextLength -= 1;
|
||||
}
|
||||
|
||||
// Add the script info
|
||||
batchInfos.Add(new BatchInfo(args.Batch.TextSpan.iStartLine, args.Batch.TextSpan.iStartIndex, batchTextLength, args.Batch.ExpectedExecutionCount));
|
||||
}
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
// intentionally swallow
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// adding this for debugging
|
||||
Logger.Write(TraceEventType.Warning, "Exception Caught in BatchParserWrapper.OnBatchParserExecutionFinished(...)" + e.ToString());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal BatchEventHandlers class
|
||||
|
||||
/// <summary>
|
||||
/// Internal implementation class to implement IBatchEventHandlers
|
||||
/// </summary>
|
||||
internal class BatchEventNotificationHandler : IBatchEventsHandler
|
||||
{
|
||||
public void OnBatchError(object sender, BatchErrorEventArgs args)
|
||||
{
|
||||
if (args != null)
|
||||
{
|
||||
|
||||
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineError);
|
||||
throw new Exception(SR.BatchParserWrapperExecutionEngineError);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBatchMessage(object sender, BatchMessageEventArgs args)
|
||||
{
|
||||
#if DEBUG
|
||||
if (args != null)
|
||||
{
|
||||
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchMessage);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnBatchResultSetProcessing(object sender, BatchResultSetEventArgs args)
|
||||
{
|
||||
#if DEBUG
|
||||
if (args != null && args.DataReader != null)
|
||||
{
|
||||
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchResultSetProcessing);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnBatchResultSetFinished(object sender, EventArgs args)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchResultSetFinished);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnBatchCancelling(object sender, EventArgs args)
|
||||
{
|
||||
Logger.Write(TraceEventType.Information, SR.BatchParserWrapperExecutionEngineBatchCancelling);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable implementation
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (executionEngine != null)
|
||||
{
|
||||
executionEngine.Dispose();
|
||||
executionEngine = null;
|
||||
batchInfos = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private class BatchInfo
|
||||
{
|
||||
public BatchInfo(int startLine, int startColumn, int length, int repeatCount = 1)
|
||||
{
|
||||
this.startLine = startLine;
|
||||
this.startColumn = startColumn;
|
||||
this.length = length;
|
||||
this.executionCount = repeatCount;
|
||||
}
|
||||
public int startLine;
|
||||
public int startColumn;
|
||||
public int length;
|
||||
public int executionCount;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>This exception is used to report that can come from the Batch Parser inside BatchParserWrapper.</summary>
|
||||
internal sealed class BatchParserWrapperException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Get description of the BatchParserWrapperException
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public enum ErrorCode
|
||||
{
|
||||
ErrorCodeBase = 0,
|
||||
|
||||
Success = ErrorCodeBase,
|
||||
// Lexer error codes
|
||||
UnsupportedCommand = ErrorCodeBase + 1,
|
||||
UnrecognizedToken = ErrorCodeBase + 2,
|
||||
StringNotTerminated = ErrorCodeBase + 3,
|
||||
CommentNotTerminated = ErrorCodeBase + 4,
|
||||
|
||||
// Parser error codes
|
||||
InvalidVariableName = ErrorCodeBase + 6,
|
||||
InvalidNumber = ErrorCodeBase + 7,
|
||||
TokenExpected = ErrorCodeBase + 8,
|
||||
Aborted = ErrorCodeBase + 9,
|
||||
CircularReference = ErrorCodeBase + 10,
|
||||
VariableNotDefined = ErrorCodeBase + 11,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,892 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.SqlTools.ManagedBatchParser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Single batch of SQL command
|
||||
/// </summary>
|
||||
public class Batch
|
||||
{
|
||||
#region Private methods
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to format the provided SqlError
|
||||
/// </summary>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
private string FormatSqlErrorMessage(SqlError error)
|
||||
{
|
||||
string detailedMessage = string.Empty;
|
||||
|
||||
if (error.Class > 10)
|
||||
{
|
||||
if (string.IsNullOrEmpty(error.Procedure))
|
||||
{
|
||||
detailedMessage = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchSqlMessageNoProcedureInfo,
|
||||
error.Number,
|
||||
error.Class,
|
||||
error.State,
|
||||
error.LineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
detailedMessage = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchSqlMessageWithProcedureInfo,
|
||||
error.Number,
|
||||
error.Class,
|
||||
error.State,
|
||||
error.Procedure,
|
||||
error.LineNumber);
|
||||
}
|
||||
}
|
||||
else if (error.Class > 0 && error.Number > 0)
|
||||
{
|
||||
detailedMessage = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchSqlMessageNoLineInfo,
|
||||
error.Number,
|
||||
error.Class,
|
||||
error.State);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(detailedMessage) && !isSuppressProviderMessageHeaders)
|
||||
{
|
||||
detailedMessage = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", error.Source, detailedMessage);
|
||||
}
|
||||
|
||||
return detailedMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a Sql exception
|
||||
/// </summary>
|
||||
/// <param name="ex"></param>
|
||||
/// <returns></returns>
|
||||
private ScriptExecutionResult HandleSqlException(SqlException ex)
|
||||
{
|
||||
ScriptExecutionResult result;
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if (state == BatchState.Cancelling)
|
||||
{
|
||||
result = ScriptExecutionResult.Cancel;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ScriptExecutionResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != ScriptExecutionResult.Cancel)
|
||||
{
|
||||
HandleSqlMessages(ex.Errors);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an error message came from SqlClient
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="description"></param>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="textSpan"></param>
|
||||
private void RaiseBatchError(string message, SqlError error, TextSpan textSpan)
|
||||
{
|
||||
BatchErrorEventArgs args = new BatchErrorEventArgs(message, error, textSpan, null);
|
||||
RaiseBatchError(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an error message came from SqlClient
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
private void RaiseBatchError(BatchErrorEventArgs e)
|
||||
{
|
||||
EventHandler<BatchErrorEventArgs> cache = BatchError;
|
||||
if (cache != null)
|
||||
{
|
||||
cache(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a message came from SqlClient
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Additionally, it's being used to notify the user that the script execution
|
||||
/// has been finished.
|
||||
/// </remarks>
|
||||
/// <param name="detailedMessage"></param>
|
||||
/// <param name="message"></param>
|
||||
private void RaiseBatchMessage(string detailedMessage, string message, SqlError error)
|
||||
{
|
||||
EventHandler<BatchMessageEventArgs> cache = BatchMessage;
|
||||
if (cache != null)
|
||||
{
|
||||
BatchMessageEventArgs args = new BatchMessageEventArgs(detailedMessage, message, error);
|
||||
cache(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a new result set has to be processed
|
||||
/// </summary>
|
||||
/// <param name="resultSet"></param>
|
||||
private void RaiseBatchResultSetProcessing(IDataReader dataReader, ShowPlanType expectedShowPlan)
|
||||
{
|
||||
EventHandler<BatchResultSetEventArgs> cache = BatchResultSetProcessing;
|
||||
if (cache != null)
|
||||
{
|
||||
BatchResultSetEventArgs args = new BatchResultSetEventArgs(dataReader, expectedShowPlan);
|
||||
BatchResultSetProcessing(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the result set has been processed
|
||||
/// </summary>
|
||||
private void RaiseBatchResultSetFinished()
|
||||
{
|
||||
EventHandler<EventArgs> cache = BatchResultSetFinished;
|
||||
if (cache != null)
|
||||
{
|
||||
cache(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the batch is being cancelled with an active result set
|
||||
/// </summary>
|
||||
private void RaiseCancelling()
|
||||
{
|
||||
EventHandler<EventArgs> cache = BatchCancelling;
|
||||
if (cache != null)
|
||||
{
|
||||
cache(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private enums
|
||||
|
||||
private enum BatchState
|
||||
{
|
||||
Initial,
|
||||
Executing,
|
||||
Executed,
|
||||
ProcessingResults,
|
||||
Cancelling,
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private fields
|
||||
|
||||
// correspond to public properties
|
||||
private bool isSuppressProviderMessageHeaders;
|
||||
private bool isResultExpected = false;
|
||||
private string sqlText = string.Empty;
|
||||
private int execTimeout = 30;
|
||||
private int scriptTrackingId = 0;
|
||||
private bool isScriptExecutionTracked = false;
|
||||
private const int ChangeDatabase = 0x1645;
|
||||
|
||||
//command that will be used for execution
|
||||
private IDbCommand command = null;
|
||||
|
||||
//current object state
|
||||
private BatchState state = BatchState.Initial;
|
||||
|
||||
//script text to be executed
|
||||
private TextSpan textSpan;
|
||||
|
||||
//index of the batch in collection of batches
|
||||
private int index = 0;
|
||||
|
||||
private int expectedExecutionCount = 1;
|
||||
|
||||
private long totalAffectedRows = 0;
|
||||
|
||||
private bool hasErrors;
|
||||
|
||||
// Expected showplan if any
|
||||
private ShowPlanType expectedShowPlan;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public Batch()
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and initializes a batch object
|
||||
/// </summary>
|
||||
/// <param name="isResultExpected">Whether it is one of "set [something] on/off" type of command,
|
||||
/// that doesn't return any results from the server
|
||||
/// </param>
|
||||
/// <param name="sqlText">Text of the batch</param>
|
||||
/// <param name="execTimeout">Timeout for the batch execution. 0 means no limit </param>
|
||||
public Batch(string sqlText, bool isResultExpected, int execTimeout)
|
||||
{
|
||||
this.isResultExpected = isResultExpected;
|
||||
this.sqlText = sqlText;
|
||||
this.execTimeout = execTimeout;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Is the Batch's text valid?
|
||||
/// </summary>
|
||||
public bool HasValidText
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(sqlText);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SQL text that to be executed in the Batch
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return sqlText;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
sqlText = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether batch execution returns any results
|
||||
/// </summary>
|
||||
public bool IsResultsExpected
|
||||
{
|
||||
get
|
||||
{
|
||||
return isResultExpected;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
isResultExpected = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the execution timeout for the batch
|
||||
/// </summary>
|
||||
public int ExecutionTimeout
|
||||
{
|
||||
get
|
||||
{
|
||||
return execTimeout;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
execTimeout = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the textspan to wich the batch belongs to
|
||||
/// </summary>
|
||||
public TextSpan TextSpan
|
||||
{
|
||||
get
|
||||
{
|
||||
return textSpan;
|
||||
}
|
||||
set
|
||||
{
|
||||
textSpan = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the batch index in the collection of batches being executed
|
||||
/// </summary>
|
||||
public int BatchIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
index = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of times this batch is expected to be executed. Will be 1 by default, but for statements
|
||||
/// with "GO 2" or other numerical values, will have a number > 1
|
||||
/// </summary>
|
||||
public int ExpectedExecutionCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return expectedExecutionCount;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
expectedExecutionCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns how many rows were affected. It should be the value that can be shown
|
||||
/// in the UI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It can be used only after the execution of the batch is finished
|
||||
/// </remarks>
|
||||
public long RowsAffected
|
||||
{
|
||||
get
|
||||
{
|
||||
return totalAffectedRows;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the error.Source should be used when messages are written
|
||||
/// </summary>
|
||||
public bool IsSuppressProviderMessageHeaders
|
||||
{
|
||||
get
|
||||
{
|
||||
return isSuppressProviderMessageHeaders;
|
||||
}
|
||||
set
|
||||
{
|
||||
isSuppressProviderMessageHeaders = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the id of the script we are tracking
|
||||
/// </summary>
|
||||
public int ScriptTrackingId
|
||||
{
|
||||
get
|
||||
{
|
||||
return scriptTrackingId;
|
||||
}
|
||||
set
|
||||
{
|
||||
scriptTrackingId = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public events
|
||||
|
||||
/// <summary>
|
||||
/// fired when there is an error message from the server
|
||||
/// </summary>
|
||||
public event EventHandler<BatchErrorEventArgs> BatchError = null;
|
||||
|
||||
/// <summary>
|
||||
/// fired when there is a message from the server
|
||||
/// </summary>
|
||||
public event EventHandler<BatchMessageEventArgs> BatchMessage = null;
|
||||
|
||||
/// <summary>
|
||||
/// fired when there is a new result set available. It is guarnteed
|
||||
/// to be fired from the same thread that called Execute method
|
||||
/// </summary>
|
||||
public event EventHandler<BatchResultSetEventArgs> BatchResultSetProcessing = null;
|
||||
|
||||
/// <summary>
|
||||
/// fired when the batch recieved cancel request BEFORE it
|
||||
/// initiates cancel operation. Note that it is fired from a
|
||||
/// different thread then the one used to kick off execution
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs> BatchCancelling = null;
|
||||
|
||||
/// <summary>
|
||||
/// fired when we've done absolutely all actions for the current result set
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs> BatchResultSetFinished = null;
|
||||
#endregion
|
||||
|
||||
#region Public methods
|
||||
|
||||
/// <summary>
|
||||
/// Resets the object to its initial state
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
state = BatchState.Initial;
|
||||
command = null;
|
||||
textSpan = new TextSpan();
|
||||
totalAffectedRows = 0;
|
||||
hasErrors = false;
|
||||
expectedShowPlan = ShowPlanType.None;
|
||||
isSuppressProviderMessageHeaders = false;
|
||||
scriptTrackingId = 0;
|
||||
isScriptExecutionTracked = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the batch
|
||||
/// </summary>
|
||||
/// <param name="connection">Connection to use</param>
|
||||
/// <param name="expectedShowPlan">ShowPlan type to be used</param>
|
||||
/// <returns>result of execution</returns>
|
||||
/// <remarks>
|
||||
/// It does not return until execution is finished
|
||||
/// We may have received a Cancel request by the time this function is called
|
||||
/// </remarks>
|
||||
public ScriptExecutionResult Execute(SqlConnection connection, ShowPlanType expectedShowPlan)
|
||||
{
|
||||
// FUTURE CLEANUP: Remove in favor of general signature (IDbConnection) - #920978
|
||||
return Execute((IDbConnection)connection, expectedShowPlan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the batch
|
||||
/// </summary>
|
||||
/// <param name="connection">Connection to use</param>
|
||||
/// <param name="expectedShowPlan">ShowPlan type to be used</param>
|
||||
/// <returns>result of execution</returns>
|
||||
/// <remarks>
|
||||
/// It does not return until execution is finished
|
||||
/// We may have received a Cancel request by the time this function is called
|
||||
/// </remarks>
|
||||
public ScriptExecutionResult Execute(IDbConnection connection, ShowPlanType expectedShowPlan)
|
||||
{
|
||||
|
||||
Validate.IsNotNull(nameof(connection), connection);
|
||||
|
||||
//makes sure that the batch is not in use
|
||||
lock (this)
|
||||
{
|
||||
Debug.Assert(command == null, "SQLCommand is NOT null");
|
||||
if (command != null)
|
||||
{
|
||||
command = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.expectedShowPlan = expectedShowPlan;
|
||||
|
||||
return DoBatchExecutionImpl(connection, sqlText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the batch
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When batch is actually cancelled, Execute() will return with the appropiate status
|
||||
/// </remarks>
|
||||
public void Cancel()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (state != BatchState.Cancelling)
|
||||
{
|
||||
state = BatchState.Cancelling;
|
||||
|
||||
RaiseCancelling();
|
||||
|
||||
if (command != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
command.Cancel();
|
||||
|
||||
Debug.WriteLine("Batch.Cancel: command.Cancel completed");
|
||||
}
|
||||
catch (SqlException)
|
||||
{
|
||||
// eat it
|
||||
}
|
||||
catch (RetryLimitExceededException)
|
||||
{
|
||||
// eat it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected methods
|
||||
|
||||
/// <summary>
|
||||
/// Fires an error message event
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception caught</param>
|
||||
/// <remarks>
|
||||
/// Non-SQL exception
|
||||
/// </remarks>
|
||||
protected void HandleExceptionMessage(Exception ex)
|
||||
{
|
||||
BatchErrorEventArgs args = new BatchErrorEventArgs(string.Format(CultureInfo.CurrentCulture, SR.EE_BatchError_Exception, ex.Message), ex);
|
||||
RaiseBatchError(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires a message event
|
||||
/// </summary>
|
||||
/// <param name="errors">SqlClient errors collection</param>
|
||||
/// <remarks>
|
||||
/// Sql specific messages.
|
||||
/// </remarks>
|
||||
protected void HandleSqlMessages(SqlErrorCollection errors)
|
||||
{
|
||||
foreach (SqlError error in errors)
|
||||
{
|
||||
if (error.Number == ChangeDatabase)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string detailedMessage = FormatSqlErrorMessage(error);
|
||||
|
||||
if (error.Class > 10)
|
||||
{
|
||||
// expose this event as error
|
||||
Debug.Assert(detailedMessage.Length != 0);
|
||||
RaiseBatchError(detailedMessage, error, textSpan);
|
||||
|
||||
//at least one error message has been used
|
||||
hasErrors = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseBatchMessage(detailedMessage, error.Message, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// method that will be passed as delegate to SqlConnection.InfoMessage
|
||||
/// </summary>
|
||||
protected void OnSqlInfoMessageCallback(object sender, SqlInfoMessageEventArgs e)
|
||||
{
|
||||
HandleSqlMessages(e.Errors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegete for SqlCommand.RecordsAffected
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// This is exposed as a regular message
|
||||
/// </remarks>
|
||||
protected void OnStatementExecutionFinished(object sender, StatementCompletedEventArgs e)
|
||||
{
|
||||
string message = string.Format(CultureInfo.CurrentCulture, SR.EE_BatchExecutionInfo_RowsAffected,
|
||||
e.RecordCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
RaiseBatchMessage(message, message, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on a new ResultSet on the data reader
|
||||
/// </summary>
|
||||
/// <param name="dataReader">True if result set consumed, false on a Cancel request</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// The GridStorageResultSet created is owned by the batch consumer. It's only created here.
|
||||
/// Additionally, when BatchResultSet event handler is called, it won't return until
|
||||
/// all data is prcessed or the data being processed is terminated (i.e. cancel or error)
|
||||
/// </remarks>
|
||||
protected ScriptExecutionResult ProcessResultSet(IDataReader dataReader)
|
||||
{
|
||||
if (dataReader == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
Debug.WriteLine("ProcessResultSet: result set has been created");
|
||||
|
||||
//initialize result variable that will be set by batch consumer
|
||||
ScriptExecutionResult scriptExecutionResult = ScriptExecutionResult.Success;
|
||||
|
||||
RaiseBatchResultSetProcessing(dataReader, expectedShowPlan);
|
||||
|
||||
if (state != BatchState.Cancelling)
|
||||
{
|
||||
return scriptExecutionResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ScriptExecutionResult.Cancel;
|
||||
}
|
||||
}
|
||||
|
||||
// FUTURE CLEANUP: Remove in favor of general signature (IDbConnection) - #920978
|
||||
protected ScriptExecutionResult DoBatchExecution(SqlConnection connection, string script)
|
||||
{
|
||||
return DoBatchExecutionImpl(connection, script);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities"), SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
[SuppressMessage("Microsoft.Usage", "CA2219:DoNotRaiseExceptionsInExceptionClauses")]
|
||||
private ScriptExecutionResult DoBatchExecutionImpl(IDbConnection connection, string script)
|
||||
{
|
||||
Validate.IsNotNull(nameof(connection), connection);
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if (state == BatchState.Cancelling)
|
||||
{
|
||||
state = BatchState.Initial;
|
||||
return ScriptExecutionResult.Cancel;
|
||||
}
|
||||
}
|
||||
|
||||
ScriptExecutionResult result = ScriptExecutionResult.Success;
|
||||
|
||||
// SqlClient event handlers setup
|
||||
SqlInfoMessageEventHandler messageHandler = new SqlInfoMessageEventHandler(OnSqlInfoMessageCallback);
|
||||
StatementCompletedEventHandler statementCompletedHandler = null;
|
||||
|
||||
DbConnectionWrapper connectionWrapper = new DbConnectionWrapper(connection);
|
||||
connectionWrapper.InfoMessage += messageHandler;
|
||||
|
||||
IDbCommand command = connection.CreateCommand();
|
||||
command.CommandText = script;
|
||||
command.CommandTimeout = execTimeout;
|
||||
|
||||
DbCommandWrapper commandWrapper = null;
|
||||
if (isScriptExecutionTracked && DbCommandWrapper.IsSupportedCommand(command))
|
||||
{
|
||||
statementCompletedHandler = new StatementCompletedEventHandler(OnStatementExecutionFinished);
|
||||
commandWrapper = new DbCommandWrapper(command);
|
||||
commandWrapper.StatementCompleted += statementCompletedHandler;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
state = BatchState.Executing;
|
||||
this.command = command;
|
||||
command = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result = this.ExecuteCommand();
|
||||
}
|
||||
catch (OutOfMemoryException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (SqlException sqlEx)
|
||||
{
|
||||
result = HandleSqlException(sqlEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = ScriptExecutionResult.Failure;
|
||||
HandleExceptionMessage(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
if (messageHandler == null)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, "Expected handler to be declared");
|
||||
}
|
||||
|
||||
if (null != connectionWrapper)
|
||||
{
|
||||
connectionWrapper.InfoMessage -= messageHandler;
|
||||
}
|
||||
|
||||
if (commandWrapper != null)
|
||||
{
|
||||
|
||||
if (statementCompletedHandler == null)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, "Expect handler to be declared if we have a command wrapper");
|
||||
}
|
||||
commandWrapper.StatementCompleted -= statementCompletedHandler;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
state = BatchState.Initial;
|
||||
if (command != null)
|
||||
{
|
||||
command.Dispose();
|
||||
command = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ScriptExecutionResult ExecuteCommand()
|
||||
{
|
||||
if (command == null)
|
||||
{
|
||||
throw new ArgumentNullException("command");
|
||||
}
|
||||
|
||||
return this.ExecuteUnTrackedCommand();
|
||||
|
||||
}
|
||||
|
||||
private ScriptExecutionResult ExecuteUnTrackedCommand()
|
||||
{
|
||||
IDataReader reader = null;
|
||||
|
||||
if (!isResultExpected)
|
||||
{
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
else
|
||||
{
|
||||
reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
|
||||
}
|
||||
|
||||
return this.CheckStateAndRead(reader);
|
||||
}
|
||||
|
||||
private ScriptExecutionResult CheckStateAndRead(IDataReader reader = null)
|
||||
{
|
||||
ScriptExecutionResult result = ScriptExecutionResult.Success;
|
||||
|
||||
if (!isResultExpected)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (state == BatchState.Cancelling)
|
||||
{
|
||||
result = ScriptExecutionResult.Cancel;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ScriptExecutionResult.Success;
|
||||
state = BatchState.Executed;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (state == BatchState.Cancelling)
|
||||
{
|
||||
result = ScriptExecutionResult.Cancel;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = BatchState.ProcessingResults;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != ScriptExecutionResult.Cancel)
|
||||
{
|
||||
ScriptExecutionResult batchExecutionResult = ScriptExecutionResult.Success;
|
||||
|
||||
if (reader != null)
|
||||
{
|
||||
bool hasNextResult = false;
|
||||
do
|
||||
{
|
||||
// if there were no results coming from the server, then the FieldCount is 0
|
||||
if (reader.FieldCount <= 0)
|
||||
{
|
||||
hasNextResult = reader.NextResult();
|
||||
continue;
|
||||
}
|
||||
|
||||
batchExecutionResult = ProcessResultSet(reader);
|
||||
|
||||
if (batchExecutionResult != ScriptExecutionResult.Success)
|
||||
{
|
||||
result = batchExecutionResult;
|
||||
break;
|
||||
}
|
||||
|
||||
RaiseBatchResultSetFinished();
|
||||
|
||||
hasNextResult = reader.NextResult();
|
||||
|
||||
} while (hasNextResult);
|
||||
}
|
||||
|
||||
if (hasErrors)
|
||||
{
|
||||
Debug.WriteLine("DoBatchExecution: successfull processed result set, but there were errors shown to the user");
|
||||
result = ScriptExecutionResult.Failure;
|
||||
}
|
||||
|
||||
if (result != ScriptExecutionResult.Cancel)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
state = BatchState.Executed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reader != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// reader.Close() doesn't actually close the reader
|
||||
// so explicitly dispose the reader
|
||||
reader.Dispose();
|
||||
reader = null;
|
||||
}
|
||||
catch (OutOfMemoryException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (SqlException)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
///<summary>
|
||||
/// Class to get text from the BatchParser and convert them into batches
|
||||
///</summary>
|
||||
public class BatchDefinition
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for a BatchDefinition
|
||||
/// </summary>
|
||||
public BatchDefinition(string batchText, int startLine, int endLine, int startColumn, int endColumn, int executionCount)
|
||||
{
|
||||
BatchText = batchText;
|
||||
StartLine = startLine;
|
||||
EndLine = endLine;
|
||||
StartColumn = startColumn;
|
||||
EndColumn = endColumn;
|
||||
// set the batch execution count, with min value of 1
|
||||
BatchExecutionCount = executionCount > 0 ? executionCount : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get starting line of the BatchDefinition
|
||||
/// </summary>
|
||||
public int StartLine
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get ending line of the BatchDefinition
|
||||
/// </summary>
|
||||
public int EndLine
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get starting column of the BatchDefinition
|
||||
/// </summary>
|
||||
public int StartColumn
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get ending column of the BatchDefinition
|
||||
/// </summary>
|
||||
public int EndColumn
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get batch text associated with the BatchDefinition
|
||||
/// </summary>
|
||||
public string BatchText
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get number of times to execute this batch
|
||||
/// </summary>
|
||||
public int BatchExecutionCount
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlTools.ManagedBatchParser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Error totalAffectedRows for a Batch
|
||||
/// </summary>
|
||||
public class BatchErrorEventArgs : EventArgs
|
||||
{
|
||||
#region Private Fields
|
||||
private string message = string.Empty;
|
||||
private string description = string.Empty;
|
||||
private int line = -1;
|
||||
private TextSpan textSpan;
|
||||
private Exception exception;
|
||||
private SqlError error;
|
||||
#endregion
|
||||
|
||||
#region Constructors / Destructor
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
private BatchErrorEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with message and no description
|
||||
/// </summary>
|
||||
internal BatchErrorEventArgs(string message)
|
||||
: this(message, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with exception and no description
|
||||
/// </summary>
|
||||
internal BatchErrorEventArgs(string message, Exception ex)
|
||||
: this(message, string.Empty, ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with message and description
|
||||
/// </summary>
|
||||
internal BatchErrorEventArgs(string message, string description, Exception ex)
|
||||
: this(message, description, -1, new TextSpan(), ex)
|
||||
{
|
||||
}
|
||||
|
||||
internal BatchErrorEventArgs(string message, SqlError error, TextSpan textSpan, Exception ex)
|
||||
{
|
||||
string desc = error != null ? error.Message : null;
|
||||
if (error.Number == 7202)
|
||||
{
|
||||
desc += " " + Environment.NewLine + SR.TroubleshootingAssistanceMessage;
|
||||
}
|
||||
|
||||
int lineNumber = error != null ? error.LineNumber : -1;
|
||||
Init(message, desc, lineNumber, textSpan, ex);
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with message, description, textspan and line number
|
||||
/// </summary>
|
||||
internal BatchErrorEventArgs(string message, string description, int line, TextSpan textSpan, Exception ex)
|
||||
{
|
||||
Init(message, description, line, textSpan, ex);
|
||||
}
|
||||
|
||||
private void Init(string message, string description, int line, TextSpan textSpan, Exception ex)
|
||||
{
|
||||
this.message = message;
|
||||
this.description = description;
|
||||
this.line = line;
|
||||
this.textSpan = textSpan;
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
public string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public int Line
|
||||
{
|
||||
get
|
||||
{
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
public TextSpan TextSpan
|
||||
{
|
||||
get
|
||||
{
|
||||
return textSpan;
|
||||
}
|
||||
}
|
||||
|
||||
public Exception Exception
|
||||
{
|
||||
get
|
||||
{
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
|
||||
public SqlError Error
|
||||
{
|
||||
get { return error; }
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Event args for notification about non-error message
|
||||
/// </summary>
|
||||
public class BatchMessageEventArgs : EventArgs
|
||||
{
|
||||
private readonly string message = string.Empty;
|
||||
private readonly string detailedMessage = string.Empty;
|
||||
private readonly SqlError error;
|
||||
|
||||
private BatchMessageEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
internal BatchMessageEventArgs(string msg)
|
||||
: this(string.Empty, msg)
|
||||
{
|
||||
}
|
||||
|
||||
internal BatchMessageEventArgs(string detailedMsg, string msg) : this(detailedMsg, msg, null)
|
||||
{
|
||||
}
|
||||
internal BatchMessageEventArgs(string detailedMsg, string msg, SqlError error)
|
||||
{
|
||||
message = msg;
|
||||
detailedMessage = detailedMsg;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public string DetailedMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
return detailedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public SqlError Error { get { return error; } }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that parses queries into batches
|
||||
/// </summary>
|
||||
public class BatchParser :
|
||||
ICommandHandler,
|
||||
IVariableResolver
|
||||
{
|
||||
#region Private fields
|
||||
protected ScriptMessageDelegate scriptMessageDelegate;
|
||||
protected ScriptErrorDelegate scriptErrorDelegate;
|
||||
protected ExecuteDelegate executeDelegate;
|
||||
protected HaltParserDelegate haltParserDelegate;
|
||||
private int startingLine = 0;
|
||||
protected bool variableSubstitutionDisabled = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public delegates
|
||||
public delegate void HaltParserDelegate();
|
||||
public delegate void ScriptMessageDelegate(string message);
|
||||
public delegate void ScriptErrorDelegate(string message, ScriptMessageType messageType);
|
||||
public delegate bool ExecuteDelegate(string batchScript, int num, int lineNumber);
|
||||
#endregion
|
||||
|
||||
#region Constructors / Destructor
|
||||
public BatchParser()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
public ScriptMessageDelegate Message
|
||||
{
|
||||
get { return scriptMessageDelegate; }
|
||||
set { scriptMessageDelegate = value; }
|
||||
}
|
||||
|
||||
public ScriptErrorDelegate ErrorMessage
|
||||
{
|
||||
get { return scriptErrorDelegate; }
|
||||
set { scriptErrorDelegate = value; }
|
||||
}
|
||||
|
||||
public ExecuteDelegate Execute
|
||||
{
|
||||
get { return executeDelegate; }
|
||||
set { executeDelegate = value; }
|
||||
}
|
||||
|
||||
public HaltParserDelegate HaltParser
|
||||
{
|
||||
get { return haltParserDelegate; }
|
||||
set { haltParserDelegate = value; }
|
||||
}
|
||||
|
||||
public int StartingLine
|
||||
{
|
||||
get { return startingLine; }
|
||||
set { startingLine = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICommandHandler Members
|
||||
|
||||
/// <summary>
|
||||
/// Take approptiate action on the parsed batches
|
||||
/// </summary>
|
||||
public BatchParserAction Go(TextBlock batch, int repeatCount)
|
||||
{
|
||||
string str;
|
||||
LineInfo lineInfo;
|
||||
|
||||
batch.GetText(!variableSubstitutionDisabled, out str, out lineInfo);
|
||||
|
||||
bool executeResult = false;
|
||||
if (executeDelegate != null)
|
||||
{
|
||||
executeResult = executeDelegate(str, repeatCount, lineInfo.GetStreamPositionForOffset(0).Line + startingLine - 1);
|
||||
}
|
||||
return executeResult ? BatchParserAction.Continue : BatchParserAction.Abort;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected methods
|
||||
/// <summary>
|
||||
/// Called when the script parsing has errors/warnings
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="messageType"></param>
|
||||
protected void RaiseScriptError(string message, ScriptMessageType messageType)
|
||||
{
|
||||
if (scriptErrorDelegate != null)
|
||||
{
|
||||
scriptErrorDelegate(message, messageType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on parsing info message
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="messageType"></param>
|
||||
protected void RaiseScriptMessage(string message)
|
||||
{
|
||||
if (scriptMessageDelegate != null)
|
||||
{
|
||||
scriptMessageDelegate(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on parsing info message
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="messageType"></param>
|
||||
protected void RaiseHaltParser()
|
||||
{
|
||||
if (haltParserDelegate != null)
|
||||
{
|
||||
haltParserDelegate();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public virtual BatchParserAction OnError(Token token, OnErrorAction action)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
}
|
||||
|
||||
public virtual BatchParserAction Include(TextBlock filename, out System.IO.TextReader stream, out string newFilename)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
}
|
||||
|
||||
public virtual string GetVariable(PositionStruct pos, string name)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
}
|
||||
|
||||
public virtual void SetVariable(PositionStruct pos, string name, string value)
|
||||
{
|
||||
throw new NotImplementedException("The method or operation is not implemented.");
|
||||
}
|
||||
|
||||
public void DisableVariableSubstitution()
|
||||
{
|
||||
variableSubstitutionDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Class associated with batch parser execution errors
|
||||
/// </summary>
|
||||
public class BatchParserExecutionErrorEventArgs : BatchErrorEventArgs
|
||||
{
|
||||
private readonly ScriptMessageType messageType;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for BatchParserExecutionErrorEventArgs class
|
||||
/// </summary>
|
||||
public BatchParserExecutionErrorEventArgs(string errorLine, string message, ScriptMessageType messageType)
|
||||
: base(errorLine, message, null)
|
||||
{
|
||||
this.messageType = messageType;
|
||||
}
|
||||
|
||||
public ScriptMessageType MessageType
|
||||
{
|
||||
get
|
||||
{
|
||||
return messageType;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Class associated with batch parser execution finished event
|
||||
/// </summary>
|
||||
public class BatchParserExecutionFinishedEventArgs : EventArgs
|
||||
{
|
||||
|
||||
private readonly Batch batch = null;
|
||||
private readonly ScriptExecutionResult result;
|
||||
|
||||
private BatchParserExecutionFinishedEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for the class
|
||||
/// </summary>
|
||||
public BatchParserExecutionFinishedEventArgs(ScriptExecutionResult batchResult, Batch batch)
|
||||
{
|
||||
this.batch = batch;
|
||||
result = batchResult;
|
||||
}
|
||||
|
||||
public Batch Batch
|
||||
{
|
||||
get
|
||||
{
|
||||
return batch;
|
||||
}
|
||||
}
|
||||
|
||||
public ScriptExecutionResult ExecutionResult
|
||||
{
|
||||
get
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Class associated with batch parser execution start event
|
||||
/// </summary>
|
||||
public class BatchParserExecutionStartEventArgs : EventArgs
|
||||
{
|
||||
|
||||
private readonly Batch batch = null;
|
||||
private readonly TextSpan textSpan;
|
||||
|
||||
private BatchParserExecutionStartEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contructor method for the class
|
||||
/// </summary>
|
||||
public BatchParserExecutionStartEventArgs(TextSpan textSpan, Batch batch)
|
||||
{
|
||||
this.batch = batch;
|
||||
this.textSpan = textSpan;
|
||||
}
|
||||
|
||||
public Batch Batch
|
||||
{
|
||||
get
|
||||
{
|
||||
return batch;
|
||||
}
|
||||
}
|
||||
|
||||
public TextSpan TextSpan
|
||||
{
|
||||
get
|
||||
{
|
||||
return textSpan;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ManagedBatchParser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for handling SQL CMD by Batch Parser
|
||||
/// </summary>
|
||||
public class BatchParserSqlCmd : BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// The internal variables that can be used in SqlCommand substitution.
|
||||
/// These variables take precedence over environment variables.
|
||||
/// </summary>
|
||||
private Dictionary<string, string> internalVariables = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
|
||||
private ConnectionChangedDelegate connectionChangedDelegate;
|
||||
private ErrorActionChangedDelegate errorActionChangedDelegate;
|
||||
|
||||
public delegate void ConnectionChangedDelegate(SqlConnectionStringBuilder connectionstringBuilder);
|
||||
public delegate void ErrorActionChangedDelegate(OnErrorAction ea);
|
||||
|
||||
/// <summary>
|
||||
/// Constructor taking a Parser instance
|
||||
/// </summary>
|
||||
/// <param name="parser"></param>
|
||||
public BatchParserSqlCmd()
|
||||
: base()
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
public ConnectionChangedDelegate ConnectionChanged
|
||||
{
|
||||
get { return connectionChangedDelegate; }
|
||||
set { connectionChangedDelegate = value; }
|
||||
}
|
||||
|
||||
public ErrorActionChangedDelegate ErrorActionChanged
|
||||
{
|
||||
get { return errorActionChangedDelegate; }
|
||||
set { errorActionChangedDelegate = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for any environment variable or internal variable.
|
||||
/// </summary>
|
||||
public override string GetVariable(PositionStruct pos, string name)
|
||||
{
|
||||
if (variableSubstitutionDisabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string value;
|
||||
|
||||
// Internally defined variables have higher precedence over environment variables.
|
||||
if (!internalVariables.TryGetValue(name, out value))
|
||||
{
|
||||
value = Environment.GetEnvironmentVariables()[name] as string;
|
||||
}
|
||||
if (value == null)
|
||||
{
|
||||
RaiseScriptError(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionError_VariableNotFound, name), ScriptMessageType.FatalError);
|
||||
RaiseHaltParser();
|
||||
// TODO: Halt the parser, should get/set variable have ParserAction.Abort/Continue (like original?)
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set environment or internal variable
|
||||
/// </summary>
|
||||
public override void SetVariable(PositionStruct pos, string name, string value)
|
||||
{
|
||||
if (variableSubstitutionDisabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
if (internalVariables.ContainsKey(name))
|
||||
{
|
||||
internalVariables.Remove(name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
internalVariables[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> InternalVariables
|
||||
{
|
||||
get { return internalVariables; }
|
||||
set { internalVariables = value; }
|
||||
}
|
||||
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ppIBatchSource")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "fileName")]
|
||||
public override BatchParserAction Include(TextBlock filename, out TextReader stream, out string newFilename)
|
||||
{
|
||||
stream = null;
|
||||
newFilename = null;
|
||||
|
||||
RaiseScriptError(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionError_CommandNotSupported, "Include"), ScriptMessageType.Error);
|
||||
return BatchParserAction.Abort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to deal with errors
|
||||
/// </summary>
|
||||
public override BatchParserAction OnError(Token token, OnErrorAction ea)
|
||||
{
|
||||
if (errorActionChangedDelegate != null)
|
||||
{
|
||||
errorActionChangedDelegate(ea);
|
||||
}
|
||||
return BatchParserAction.Continue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Class associated with setting batch results
|
||||
/// </summary>
|
||||
public class BatchResultSetEventArgs : EventArgs
|
||||
{
|
||||
|
||||
private readonly IDataReader dataReader = null;
|
||||
private readonly ShowPlanType expectedShowPlan = ShowPlanType.None;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="dataReader"></param>
|
||||
internal BatchResultSetEventArgs(IDataReader dataReader, ShowPlanType expectedShowPlan)
|
||||
{
|
||||
this.dataReader = dataReader;
|
||||
this.expectedShowPlan = expectedShowPlan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data reader associated with the result set
|
||||
/// </summary>
|
||||
public IDataReader DataReader
|
||||
{
|
||||
get
|
||||
{
|
||||
return dataReader;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show Plan to be expected if any during the execution
|
||||
/// </summary>
|
||||
public ShowPlanType ExpectedShowPlan
|
||||
{
|
||||
get
|
||||
{
|
||||
return expectedShowPlan;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Specialized;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
public class ExecutionEngineConditions
|
||||
{
|
||||
|
||||
#region Private fields
|
||||
private static class Consts
|
||||
{
|
||||
public static string On = "ON";
|
||||
public static string Off = "OFF";
|
||||
public static string ParseOnly = "SET PARSEONLY {0}";
|
||||
public static string NoExec = "SET NOEXEC {0}";
|
||||
public static string StatisticsIO = "SET STATISTICS IO {0}";
|
||||
public static string StatisticsTime = "SET STATISTICS TIME {0}";
|
||||
public static string ShowPlanXml = "SET SHOWPLAN_XML {0}";
|
||||
public static string ShowPlanAll = "SET SHOWPLAN_ALL {0}";
|
||||
public static string ShowPlanText = "SET SHOWPLAN_TEXT {0}";
|
||||
public static string StatisticsXml = "SET STATISTICS XML {0}";
|
||||
public static string StatisticsProfile = "SET STATISTICS PROFILE {0}";
|
||||
public static string BeginTrans = "BEGIN TRAN";
|
||||
public static string CommitTrans = "COMMIT TRAN";
|
||||
public static string Rollback = "ROLLBACK";
|
||||
public static string BatchSeparator = "GO";
|
||||
|
||||
public static string Reset = "SET NOEXEC, FMTONLY OFF, PARSEONLY, SET SHOWPLAN_ALL, SET SHOWPLAN_TEXT";
|
||||
}
|
||||
|
||||
private static readonly int stateParseOnly = BitVector32.CreateMask();
|
||||
private static readonly int stateTransactionWrapped = BitVector32.CreateMask(stateParseOnly);
|
||||
private static readonly int stateHaltOnError = BitVector32.CreateMask(stateTransactionWrapped);
|
||||
private static readonly int stateEstimatedShowPlan = BitVector32.CreateMask(stateHaltOnError);
|
||||
private static readonly int stateActualShowPlan = BitVector32.CreateMask(stateEstimatedShowPlan);
|
||||
private static readonly int stateSuppressProviderMessageHeaders = BitVector32.CreateMask(stateActualShowPlan);
|
||||
private static readonly int stateNoExec = BitVector32.CreateMask(stateSuppressProviderMessageHeaders);
|
||||
private static readonly int stateStatisticsIO = BitVector32.CreateMask(stateNoExec);
|
||||
private static readonly int stateShowPlanText = BitVector32.CreateMask(stateStatisticsIO);
|
||||
private static readonly int stateStatisticsTime = BitVector32.CreateMask(stateShowPlanText);
|
||||
private static readonly int stateSqlCmd = BitVector32.CreateMask(stateStatisticsTime);
|
||||
private static readonly int stateScriptExecutionTracked = BitVector32.CreateMask(stateSqlCmd);
|
||||
|
||||
private BitVector32 state = new BitVector32();
|
||||
private string batchSeparator = Consts.BatchSeparator;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors / Destructor
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public ExecutionEngineConditions()
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overloaded constructor taking another ExecutionEngineCondition object as a reference
|
||||
/// </summary>
|
||||
public ExecutionEngineConditions(ExecutionEngineConditions condition)
|
||||
{
|
||||
state = condition.state;
|
||||
batchSeparator = condition.batchSeparator;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Statement strings
|
||||
|
||||
public static string ShowPlanXmlStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ShowPlanXml, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string ShowPlanAllStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ShowPlanAll, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string ShowPlanTextStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ShowPlanText, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string StatisticsXmlStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsXml, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string StatisticsProfileStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsProfile, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string ParseOnlyStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.ParseOnly, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string NoExecStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.NoExec, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string StatisticsIOStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsIO, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string StatisticsTimeStatement(bool isOn)
|
||||
{
|
||||
return string.Format(System.Globalization.CultureInfo.InvariantCulture, Consts.StatisticsTime, (isOn ? Consts.On : Consts.Off));
|
||||
}
|
||||
|
||||
public static string BeginTransactionStatement
|
||||
{
|
||||
get { return Consts.BeginTrans; }
|
||||
}
|
||||
|
||||
public static string CommitTransactionStatement
|
||||
{
|
||||
get { return Consts.CommitTrans; }
|
||||
}
|
||||
|
||||
public static string RollbackTransactionStatement
|
||||
{
|
||||
get { return Consts.Rollback; }
|
||||
}
|
||||
|
||||
public static string BatchSeparatorStatement
|
||||
{
|
||||
get { return Consts.BatchSeparator; }
|
||||
}
|
||||
|
||||
public static string ResetStatement
|
||||
{
|
||||
get { return Consts.Reset; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Checks the syntax of each Transact-SQL statement and returns any error messages
|
||||
/// without compiling or executing the statement.
|
||||
/// </summary>
|
||||
public bool IsParseOnly
|
||||
{
|
||||
get { return state[stateParseOnly]; }
|
||||
set { state[stateParseOnly] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script is wrapped withint BEGIN TRAN/COMMIT-ROLLBACK
|
||||
/// </summary>
|
||||
public bool IsTransactionWrapped
|
||||
{
|
||||
get { return state[stateTransactionWrapped]; }
|
||||
set { state[stateTransactionWrapped] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or a set a value indicating whether script execution is tracked
|
||||
/// </summary>
|
||||
public bool IsScriptExecutionTracked
|
||||
{
|
||||
get { return state[stateScriptExecutionTracked]; }
|
||||
set { state[stateScriptExecutionTracked] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Halts the execution if an error is found
|
||||
/// </summary>
|
||||
public bool IsHaltOnError
|
||||
{
|
||||
get { return state[stateHaltOnError]; }
|
||||
set { state[stateHaltOnError] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use estimated show plan
|
||||
/// </summary>
|
||||
public bool IsEstimatedShowPlan
|
||||
{
|
||||
get { return state[stateEstimatedShowPlan]; }
|
||||
set { state[stateEstimatedShowPlan] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use actual show plan
|
||||
/// </summary>
|
||||
public bool IsActualShowPlan
|
||||
{
|
||||
get { return state[stateActualShowPlan]; }
|
||||
set { state[stateActualShowPlan] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use Source information on messages shown to the user
|
||||
/// </summary>
|
||||
public bool IsSuppressProviderMessageHeaders
|
||||
{
|
||||
get { return state[stateSuppressProviderMessageHeaders]; }
|
||||
set { state[stateSuppressProviderMessageHeaders] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SET NO EXEC {on/off}
|
||||
/// </summary>
|
||||
public bool IsNoExec
|
||||
{
|
||||
get { return state[stateNoExec]; }
|
||||
set { state[stateNoExec] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SET STATISTICS IO {on/off}
|
||||
/// </summary>
|
||||
public bool IsStatisticsIO
|
||||
{
|
||||
get { return state[stateStatisticsIO]; }
|
||||
set { state[stateStatisticsIO] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SET SHOWPLAN_TEXT {on/off}
|
||||
/// </summary>
|
||||
public bool IsShowPlanText
|
||||
{
|
||||
get { return state[stateShowPlanText]; }
|
||||
set { state[stateShowPlanText] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SET STATISTICS IO {on/off}
|
||||
/// </summary>
|
||||
public bool IsStatisticsTime
|
||||
{
|
||||
get { return state[stateStatisticsTime]; }
|
||||
set { state[stateStatisticsTime] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SqlCmd support
|
||||
/// </summary>
|
||||
public bool IsSqlCmd
|
||||
{
|
||||
get { return state[stateSqlCmd]; }
|
||||
set { state[stateSqlCmd] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch separator statement
|
||||
/// </summary>
|
||||
public string BatchSeparator
|
||||
{
|
||||
get
|
||||
{
|
||||
return batchSeparator;
|
||||
}
|
||||
set
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString(nameof(value), value);
|
||||
batchSeparator = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
internal enum ExecutionState
|
||||
{
|
||||
Initial,
|
||||
Executing,
|
||||
ExecutingBatch,
|
||||
Cancelling,
|
||||
Discarded
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
public interface IBatchEventsHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// fired when there is an error message from the server
|
||||
/// </summary>
|
||||
void OnBatchError(object sender, BatchErrorEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// fired when there is a message from the server
|
||||
/// </summary>
|
||||
void OnBatchMessage(object sender, BatchMessageEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// fired when there is a new result set available. It is guarnteed
|
||||
/// to be fired from the same thread that called Execute method
|
||||
/// </summary>
|
||||
void OnBatchResultSetProcessing(object sender, BatchResultSetEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// fired when we've done absolutely all actions for the current result set
|
||||
/// </summary>
|
||||
void OnBatchResultSetFinished(object sender, EventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// fired when the batch recieved cancel request BEFORE it
|
||||
/// initiates cancel operation. Note that it is fired from a
|
||||
/// different thread then the one used to kick off execution
|
||||
/// </summary>
|
||||
void OnBatchCancelling(object sender, EventArgs args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
public class ScriptExecutionArgs : EventArgs
|
||||
{
|
||||
private IDbConnection connection;
|
||||
private IBatchEventsHandler batchEventHandlers;
|
||||
private int startingLine;
|
||||
private Dictionary<string, string> cmdVariables;
|
||||
|
||||
#region Constructors / Destructor
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for ScriptExecutionArgs
|
||||
/// </summary>
|
||||
public ScriptExecutionArgs(
|
||||
string script,
|
||||
SqlConnection connection,
|
||||
int timeOut,
|
||||
ExecutionEngineConditions conditions,
|
||||
IBatchEventsHandler batchEventHandlers)
|
||||
: this (script, (IDbConnection)connection, timeOut, conditions, batchEventHandlers)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for ScriptExecutionArgs
|
||||
/// </summary>
|
||||
public ScriptExecutionArgs(
|
||||
string script,
|
||||
SqlConnection connection,
|
||||
int timeOut,
|
||||
ExecutionEngineConditions conditions,
|
||||
IBatchEventsHandler batchEventHandlers,
|
||||
int startingLine,
|
||||
IDictionary<string,string> variables)
|
||||
: this(script, (IDbConnection) connection, timeOut, conditions, batchEventHandlers, startingLine, variables)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for ScriptExecutionArgs
|
||||
/// </summary>
|
||||
public ScriptExecutionArgs(
|
||||
string script,
|
||||
IDbConnection connection,
|
||||
int timeOut,
|
||||
ExecutionEngineConditions conditions,
|
||||
IBatchEventsHandler batchEventHandlers)
|
||||
: this(script, connection, timeOut, conditions, batchEventHandlers, 0, null)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for ScriptExecutionArgs
|
||||
/// </summary>
|
||||
public ScriptExecutionArgs(
|
||||
string script,
|
||||
IDbConnection connection,
|
||||
int timeOut,
|
||||
ExecutionEngineConditions conditions,
|
||||
IBatchEventsHandler batchEventHandlers,
|
||||
int startingLine,
|
||||
IDictionary<string, string> variables)
|
||||
{
|
||||
Script = script;
|
||||
this.connection = connection;
|
||||
TimeOut = timeOut;
|
||||
Conditions = conditions;
|
||||
this.batchEventHandlers = batchEventHandlers;
|
||||
this.startingLine = startingLine;
|
||||
|
||||
if (variables != null)
|
||||
{
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
Variables[variable.Key] = variable.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
public string Script { get; set; }
|
||||
|
||||
// FUTURE CLEANUP: Remove in favor of general signature (IDbConnection) - #920978
|
||||
public SqlConnection Connection
|
||||
{
|
||||
get { return connection as SqlConnection; }
|
||||
set { connection = value as SqlConnection; }
|
||||
}
|
||||
|
||||
public IDbConnection ReliableConnection
|
||||
{
|
||||
get { return connection; }
|
||||
set { connection = value; }
|
||||
}
|
||||
|
||||
public int TimeOut { get; set; }
|
||||
|
||||
internal ExecutionEngineConditions Conditions { get; set; }
|
||||
|
||||
internal IBatchEventsHandler BatchEventHandlers
|
||||
{
|
||||
get { return batchEventHandlers; }
|
||||
set { batchEventHandlers = value; }
|
||||
}
|
||||
|
||||
internal int StartingLine
|
||||
{
|
||||
get { return startingLine; }
|
||||
set { startingLine = value; }
|
||||
}
|
||||
|
||||
internal Dictionary<string, string> Variables
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cmdVariables == null)
|
||||
{
|
||||
cmdVariables = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
return cmdVariables;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
public class ScriptExecutionFinishedEventArgs : EventArgs
|
||||
{
|
||||
internal ScriptExecutionFinishedEventArgs(ScriptExecutionResult result)
|
||||
{
|
||||
ExecutionResult = result;
|
||||
}
|
||||
|
||||
public ScriptExecutionResult ExecutionResult
|
||||
{
|
||||
private set;
|
||||
get;
|
||||
}
|
||||
|
||||
public bool Disposing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
[Flags]
|
||||
public enum ScriptExecutionResult
|
||||
{
|
||||
Success = 0x1,
|
||||
Failure = 0x2,
|
||||
Cancel = 0x4,
|
||||
Halted = 0x8,
|
||||
All = 0x0F
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
public enum ScriptMessageType
|
||||
{
|
||||
FatalError,
|
||||
Error,
|
||||
Warning
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
[System.Flags]
|
||||
public enum ShowPlanType
|
||||
{
|
||||
None = 0x0,
|
||||
ActualExecutionShowPlan = 0x1,
|
||||
ActualXmlShowPlan = 0x2,
|
||||
EstimatedExecutionShowPlan = 0x4,
|
||||
EstimatedXmlShowPlan = 0x8,
|
||||
AllXmlShowPlan = ActualXmlShowPlan | EstimatedXmlShowPlan,
|
||||
AllExecutionShowPlan = ActualExecutionShowPlan | EstimatedExecutionShowPlan,
|
||||
AllShowPlan = AllExecutionShowPlan | AllXmlShowPlan
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode
|
||||
{
|
||||
public struct TextSpan
|
||||
{
|
||||
public int iEndIndex;
|
||||
public int iEndLine;
|
||||
public int iStartIndex;
|
||||
public int iStartLine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public interface ICommandHandler
|
||||
{
|
||||
BatchParserAction Go(TextBlock batch, int repeatCount);
|
||||
BatchParserAction OnError(Token token, OnErrorAction action);
|
||||
BatchParserAction Include(TextBlock filename, out TextReader stream, out string newFilename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public interface IVariableResolver
|
||||
{
|
||||
string GetVariable(PositionStruct pos, string name);
|
||||
void SetVariable(PositionStruct pos, string name, string value);
|
||||
}
|
||||
}
|
||||
748
src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/Lexer.cs
Normal file
748
src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/Lexer.cs
Normal file
@@ -0,0 +1,748 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ManagedBatchParser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Lexer class for the SMO Batch Parser
|
||||
/// </summary>
|
||||
public sealed class Lexer : IDisposable
|
||||
{
|
||||
private LexerInput currentInput;
|
||||
private bool popInputAtNextConsume;
|
||||
private Token currentToken;
|
||||
private ErrorCode errorCode = ErrorCode.Success;
|
||||
private readonly Stack<LexerInput> inputStack = new Stack<LexerInput>();
|
||||
private Func<bool> lexerFunc;
|
||||
private TextRuleFlags textRuleFlags;
|
||||
private PositionStruct tokenBeginPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the lexer class used by SMO Batch Parser
|
||||
/// </summary>
|
||||
public Lexer(TextReader input, string name)
|
||||
{
|
||||
currentInput = new LexerInput(input, name);
|
||||
currentToken = null;
|
||||
RecognizeSqlCmdSyntax = true;
|
||||
|
||||
SetState(RuleLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current token for the lexer
|
||||
/// </summary>
|
||||
public Token CurrentToken
|
||||
{
|
||||
get {
|
||||
return currentToken;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current token type for the lexer
|
||||
/// </summary>
|
||||
public LexerTokenType CurrentTokenType
|
||||
{
|
||||
get { return currentToken.TokenType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consume the token
|
||||
/// </summary>
|
||||
public void ConsumeToken()
|
||||
{
|
||||
if (currentInput == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool result;
|
||||
tokenBeginPosition = new PositionStruct(currentInput.CurrentLine, currentInput.CurrentColumn, currentInput.CurrentOffset, currentInput.Filename);
|
||||
|
||||
do
|
||||
{
|
||||
if (popInputAtNextConsume)
|
||||
{
|
||||
PopAndCloseInput();
|
||||
popInputAtNextConsume = false;
|
||||
}
|
||||
do
|
||||
{
|
||||
result = lexerFunc();
|
||||
} while (result == false);
|
||||
if (CurrentTokenType == LexerTokenType.Eof)
|
||||
{
|
||||
popInputAtNextConsume = true;
|
||||
if(inputStack.Count > 0)
|
||||
{
|
||||
// report as empty NewLine token
|
||||
currentToken = new Token(
|
||||
LexerTokenType.NewLine, tokenBeginPosition, tokenBeginPosition, string.Empty, tokenBeginPosition.Filename);
|
||||
}
|
||||
}
|
||||
} while (result == false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
while (inputStack.Count > 0)
|
||||
{
|
||||
PopAndCloseInput();
|
||||
}
|
||||
}
|
||||
|
||||
public void PopAndCloseInput()
|
||||
{
|
||||
if (currentInput != null)
|
||||
{
|
||||
currentInput.Dispose();
|
||||
currentInput = null;
|
||||
}
|
||||
if (inputStack.Count > 0)
|
||||
{
|
||||
currentInput = inputStack.Pop();
|
||||
SetState(RuleLine);
|
||||
}
|
||||
}
|
||||
|
||||
static string GetCircularReferenceErrorMessage(string filename)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, SR.BatchParser_CircularReference, filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push current input into the stack
|
||||
/// </summary>
|
||||
public void PushInput(TextReader reader, string name)
|
||||
{
|
||||
Debug.Assert(currentToken != null &&
|
||||
(currentToken.TokenType == LexerTokenType.NewLine || currentToken.TokenType == LexerTokenType.Eof), "New input can only be pushed after new line token or EOF");
|
||||
|
||||
if (name.Equals(currentInput.Filename, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RaiseError(ErrorCode.CircularReference, GetCircularReferenceErrorMessage(name));
|
||||
}
|
||||
|
||||
foreach (LexerInput input in inputStack)
|
||||
{
|
||||
if (name.Equals(input.Filename, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RaiseError(ErrorCode.CircularReference, GetCircularReferenceErrorMessage(name));
|
||||
}
|
||||
}
|
||||
|
||||
inputStack.Push(currentInput);
|
||||
currentInput = new LexerInput(reader, name);
|
||||
SetState(RuleLine);
|
||||
// We don't want to close the input, in that case the current file would be taken out of the
|
||||
// input stack and cycle detection won't work.
|
||||
popInputAtNextConsume = false;
|
||||
ConsumeToken();
|
||||
}
|
||||
|
||||
private void AcceptBlockComment()
|
||||
{
|
||||
char? ch;
|
||||
int nestingCount = 0;
|
||||
|
||||
Consume();
|
||||
do
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
|
||||
if (ch.HasValue == false)
|
||||
{
|
||||
RaiseError(ErrorCode.CommentNotTerminated,
|
||||
string.Format(CultureInfo.CurrentCulture, SR.BatchParser_CommentNotTerminated));
|
||||
}
|
||||
|
||||
if (ch.Value == '*')
|
||||
{
|
||||
char? ch2 = Lookahead(1);
|
||||
if (ch2.HasValue && ch2.Value == '/')
|
||||
{
|
||||
Consume();
|
||||
if (nestingCount == 0)
|
||||
{
|
||||
Consume();
|
||||
break;
|
||||
}
|
||||
nestingCount--;
|
||||
}
|
||||
}
|
||||
else if (ch.Value == '/')
|
||||
{
|
||||
char? ch2 = Lookahead(1);
|
||||
if (ch2.HasValue && ch2.Value == '*')
|
||||
{
|
||||
Consume();
|
||||
nestingCount++;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
SetToken(LexerTokenType.Comment);
|
||||
}
|
||||
|
||||
private void AcceptLineComment()
|
||||
{
|
||||
char? ch;
|
||||
|
||||
Consume();
|
||||
do
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
|
||||
if (ch.HasValue == false || IsNewLineChar(ch.Value))
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
SetToken(LexerTokenType.Comment);
|
||||
}
|
||||
|
||||
private void AcceptIdentifier()
|
||||
{
|
||||
char? ch;
|
||||
|
||||
do
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
} while (ch.HasValue && IsIdentifierChar(ch.Value));
|
||||
SetToken(LexerTokenType.Text);
|
||||
}
|
||||
|
||||
private void AcceptNewLine()
|
||||
{
|
||||
char? ch = Lookahead();
|
||||
|
||||
if (ch == '\r')
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
}
|
||||
if (ch == '\n')
|
||||
{
|
||||
Consume();
|
||||
}
|
||||
|
||||
SetToken(LexerTokenType.NewLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method reads ahead until the closingChar is found. When closingChar is found,
|
||||
/// the next character is checked. If it's the same as closingChar, the character is
|
||||
/// escaped and the method resumes looking for a non-escaped closingChar.
|
||||
/// </summary>
|
||||
private void AcceptEscapableQuotedText(char closingChar)
|
||||
{
|
||||
char? ch;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
if (!ch.HasValue)
|
||||
{
|
||||
// we reached the end without finding our closing character. that's an error.
|
||||
RaiseError(ErrorCode.StringNotTerminated, SR.BatchParser_StringNotTerminated);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch == closingChar)
|
||||
{
|
||||
// we found the closing character. we call consume to ensure the pointer is now at the subsequent character.
|
||||
Consume();
|
||||
|
||||
// Check whether the subsequent character is also closingChar.
|
||||
// If it is, that means the closingChar is escaped, so we must continue searching.
|
||||
// Otherwise, we're finished.
|
||||
char? nextChar = Lookahead();
|
||||
if (!nextChar.HasValue || nextChar != closingChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method reads ahead until the closingChar is found. This method does not allow for escaping
|
||||
/// of the closingChar.
|
||||
/// </summary>
|
||||
private void AcceptQuotedText(char closingChar)
|
||||
{
|
||||
char? ch;
|
||||
|
||||
do
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
} while (ch.HasValue && ch != closingChar);
|
||||
|
||||
if (ch.HasValue == false)
|
||||
{
|
||||
RaiseError(ErrorCode.StringNotTerminated, SR.BatchParser_StringNotTerminated);
|
||||
}
|
||||
else
|
||||
{
|
||||
Consume();
|
||||
}
|
||||
}
|
||||
|
||||
private void AcceptWhitespace()
|
||||
{
|
||||
char? ch;
|
||||
|
||||
do
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
} while (ch.HasValue && IsWhitespaceChar(ch.Value));
|
||||
|
||||
SetToken(LexerTokenType.Whitespace);
|
||||
}
|
||||
|
||||
private void Consume()
|
||||
{
|
||||
currentInput.Consume();
|
||||
}
|
||||
|
||||
private void ChangeStateToBatchCommand(Token token)
|
||||
{
|
||||
switch (token.TokenType)
|
||||
{
|
||||
case LexerTokenType.Setvar:
|
||||
SetState(RuleSetvar);
|
||||
break;
|
||||
case LexerTokenType.Go:
|
||||
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment);
|
||||
break;
|
||||
case LexerTokenType.Include:
|
||||
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeDoubleQuotedString);
|
||||
break;
|
||||
case LexerTokenType.OnError:
|
||||
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment);
|
||||
break;
|
||||
default:
|
||||
SetTextState(TextRuleFlags.ReportWhitespace);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDigit(char ch)
|
||||
{
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
private static bool IsLetter(char ch)
|
||||
{
|
||||
return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
|
||||
}
|
||||
|
||||
private bool IsNewLineChar(char ch)
|
||||
{
|
||||
return ch == '\r' || ch == '\n';
|
||||
}
|
||||
|
||||
private bool IsWhitespaceChar(char ch)
|
||||
{
|
||||
return ch == ' ' || ch == '\t';
|
||||
}
|
||||
|
||||
private char? Lookahead()
|
||||
{
|
||||
return currentInput.Lookahead();
|
||||
}
|
||||
|
||||
private char? Lookahead(int lookahead)
|
||||
{
|
||||
return currentInput.Lookahead(lookahead);
|
||||
}
|
||||
|
||||
private bool RuleError()
|
||||
{
|
||||
// lexer repeats last error
|
||||
Parser.RaiseError(errorCode, CurrentToken);
|
||||
Debug.Fail("Parser.RaiseError should throw an exception");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool RuleLine()
|
||||
{
|
||||
char? ch = Lookahead();
|
||||
|
||||
if (!ch.HasValue)
|
||||
{
|
||||
SetToken(LexerTokenType.Eof);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ch.Value)
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
AcceptWhitespace();
|
||||
return true;
|
||||
case '\r':
|
||||
case '\n':
|
||||
AcceptNewLine();
|
||||
return true;
|
||||
case ':':
|
||||
if (RecognizeSqlCmdSyntax && TryAcceptBatchCommandAndSetToken())
|
||||
{
|
||||
ChangeStateToBatchCommand(currentToken);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
case 'G':
|
||||
if (TryAccept("go", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Go);
|
||||
ChangeStateToBatchCommand(currentToken);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SetTextState(TextRuleFlags.RecognizeSingleQuotedString | TextRuleFlags.RecognizeDoubleQuotedString | TextRuleFlags.RecognizeLineComment | TextRuleFlags.RecognizeBlockComment | TextRuleFlags.RecognizeBrace);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool RuleSetvar()
|
||||
{
|
||||
char? ch = Lookahead();
|
||||
|
||||
if (ch.HasValue == false)
|
||||
{
|
||||
SetToken(LexerTokenType.Eof);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ch.Value)
|
||||
{
|
||||
case '\r':
|
||||
case '\n':
|
||||
AcceptNewLine();
|
||||
SetState(RuleLine);
|
||||
return true;
|
||||
case ' ':
|
||||
case '\t':
|
||||
AcceptWhitespace();
|
||||
return true;
|
||||
default:
|
||||
if (IsStartIdentifierChar(ch.Value))
|
||||
{
|
||||
AcceptIdentifier();
|
||||
SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeDoubleQuotedString);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// prepare error token
|
||||
do
|
||||
{
|
||||
Consume();
|
||||
ch = Lookahead();
|
||||
} while (ch != null && IsWhitespaceChar(ch.Value) == false && IsNewLineChar(ch.Value) == false);
|
||||
RaiseError(ErrorCode.UnrecognizedToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool RuleText()
|
||||
{
|
||||
char? ch = Lookahead();
|
||||
|
||||
if (ch.HasValue == false)
|
||||
{
|
||||
SetToken(LexerTokenType.Eof);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ch.HasValue)
|
||||
{
|
||||
if (IsNewLineChar(ch.Value))
|
||||
{
|
||||
AcceptNewLine();
|
||||
SetState(RuleLine);
|
||||
return true;
|
||||
}
|
||||
else if (textRuleFlags.HasFlag(TextRuleFlags.ReportWhitespace) && IsWhitespaceChar(ch.Value))
|
||||
{
|
||||
AcceptWhitespace();
|
||||
return true;
|
||||
}
|
||||
else if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBlockComment) || textRuleFlags.HasFlag(TextRuleFlags.RecognizeLineComment))
|
||||
{
|
||||
char? ch2 = Lookahead(1);
|
||||
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBlockComment) && ch == '/' && ch2 == '*')
|
||||
{
|
||||
AcceptBlockComment();
|
||||
return true;
|
||||
}
|
||||
else if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeLineComment) && ch == '-' && ch2 == '-')
|
||||
{
|
||||
AcceptLineComment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (ch.HasValue)
|
||||
{
|
||||
bool consumed = false;
|
||||
switch (ch.Value)
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
if (textRuleFlags.HasFlag(TextRuleFlags.ReportWhitespace))
|
||||
{
|
||||
SetToken(LexerTokenType.Text);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '\r':
|
||||
case '\n':
|
||||
SetToken(LexerTokenType.Text);
|
||||
return true;
|
||||
case '"':
|
||||
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeDoubleQuotedString))
|
||||
{
|
||||
AcceptQuotedText('"');
|
||||
consumed = true;
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeSingleQuotedString))
|
||||
{
|
||||
AcceptEscapableQuotedText('\'');
|
||||
consumed = true;
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBrace))
|
||||
{
|
||||
AcceptEscapableQuotedText(']');
|
||||
consumed = true;
|
||||
}
|
||||
break;
|
||||
case '-':
|
||||
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeLineComment))
|
||||
{
|
||||
char? ch2 = Lookahead(1);
|
||||
if (ch.HasValue && ch2 == '-')
|
||||
{
|
||||
SetToken(LexerTokenType.Text);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '/':
|
||||
if (textRuleFlags.HasFlag(TextRuleFlags.RecognizeBlockComment))
|
||||
{
|
||||
char? ch2 = Lookahead(1);
|
||||
if (ch.HasValue && ch2 == '*')
|
||||
{
|
||||
SetToken(LexerTokenType.Text);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (consumed == false)
|
||||
{
|
||||
Consume();
|
||||
}
|
||||
ch = Lookahead();
|
||||
}
|
||||
SetToken(LexerTokenType.Text);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RaiseError(ErrorCode code, string message = null)
|
||||
{
|
||||
SetState(RuleError);
|
||||
SetToken(LexerTokenType.Error);
|
||||
errorCode = code;
|
||||
Parser.RaiseError(errorCode, CurrentToken, message);
|
||||
}
|
||||
|
||||
private void SetState(Func<bool> lexerFunc)
|
||||
{
|
||||
this.lexerFunc = lexerFunc;
|
||||
}
|
||||
|
||||
internal void SetTextState(TextRuleFlags textRuleFlags)
|
||||
{
|
||||
this.textRuleFlags = textRuleFlags;
|
||||
SetState(RuleText);
|
||||
}
|
||||
|
||||
private void SetToken(LexerTokenType lexerTokenType)
|
||||
{
|
||||
string text = currentInput.FlushBufferedText();
|
||||
|
||||
currentToken = new Token(
|
||||
lexerTokenType,
|
||||
tokenBeginPosition,
|
||||
new PositionStruct(currentInput.CurrentLine, currentInput.CurrentColumn, currentInput.CurrentOffset, currentInput.Filename),
|
||||
text,
|
||||
currentInput.Filename);
|
||||
}
|
||||
|
||||
private bool TryAccept(string text, bool wordBoundary)
|
||||
{
|
||||
Debug.Assert(text.Length > 0);
|
||||
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
char? ch = Lookahead(i);
|
||||
if (ch.HasValue == false || char.ToLowerInvariant(ch.Value) != text[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
i++;
|
||||
} while (i < text.Length);
|
||||
|
||||
if (wordBoundary)
|
||||
{
|
||||
char? ch = Lookahead(text.Length);
|
||||
if (ch != null && IsWhitespaceChar(ch.Value) == false && IsNewLineChar(ch.Value) == false
|
||||
&& ch != '$' && ch != '/' && ch != '-' && ch != '\'' && ch != '"' && ch != '(' && ch != '[' && ch != '!')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// consume all checked characters
|
||||
for (i = 0; i < text.Length; i++)
|
||||
{
|
||||
Consume();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryAcceptBatchCommandAndSetToken()
|
||||
{
|
||||
Consume(); // colon
|
||||
|
||||
if (TryAccept("reset", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Reset);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("ed", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Ed);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("!!", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Execute);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("quit", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Quit);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("exit", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Exit);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("r", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Include);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("serverlist", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Serverlist);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("setvar", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Setvar);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("list", true))
|
||||
{
|
||||
SetToken(LexerTokenType.List);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("error", true))
|
||||
{
|
||||
SetToken(LexerTokenType.ErrorCommand);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("out", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Out);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("perftrace", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Perftrace);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("connect", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Connect);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("on error", true))
|
||||
{
|
||||
SetToken(LexerTokenType.OnError);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("help", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Help);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("xml", true))
|
||||
{
|
||||
SetToken(LexerTokenType.Xml);
|
||||
return true;
|
||||
}
|
||||
else if (TryAccept("listvar", true))
|
||||
{
|
||||
SetToken(LexerTokenType.ListVar);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
internal static bool IsIdentifierChar(char ch)
|
||||
{
|
||||
return IsLetter(ch) || IsDigit(ch) || ch == '_' || ch == '-';
|
||||
}
|
||||
|
||||
internal static bool IsStartIdentifierChar(char ch)
|
||||
{
|
||||
return IsLetter(ch) || ch == '_';
|
||||
}
|
||||
|
||||
public bool RecognizeSqlCmdSyntax { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Lexer input class used by lexer in SMO Batch Parser
|
||||
/// </summary>
|
||||
internal sealed class LexerInput : IDisposable
|
||||
{
|
||||
private readonly string filename;
|
||||
private TextReader input;
|
||||
private int currentLine;
|
||||
private int currentColumn;
|
||||
private int bufferStartOffset;
|
||||
private int currentSbOffset;
|
||||
private StringBuilder buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for the LexerInput class
|
||||
/// </summary>
|
||||
public LexerInput(TextReader reader, string filename)
|
||||
{
|
||||
input = reader;
|
||||
this.filename = filename;
|
||||
currentLine = 1;
|
||||
currentColumn = 1;
|
||||
bufferStartOffset = 0;
|
||||
currentSbOffset = 0;
|
||||
buffer = new StringBuilder();
|
||||
EnsureBytes(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get filename associated with lexer input
|
||||
/// </summary>
|
||||
public string Filename
|
||||
{
|
||||
get { return filename; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current line associated with lexer input
|
||||
/// </summary>
|
||||
public int CurrentLine
|
||||
{
|
||||
get { return currentLine; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current column associated with lexer input
|
||||
/// </summary>
|
||||
public int CurrentColumn
|
||||
{
|
||||
get { return currentColumn; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consume token used by lexer input
|
||||
/// </summary>
|
||||
public void Consume()
|
||||
{
|
||||
bool newLineWithCR = false;
|
||||
|
||||
char? ch = Lookahead();
|
||||
if (ch == null)
|
||||
{
|
||||
// end of stream
|
||||
return;
|
||||
}
|
||||
else if (ch == '\r')
|
||||
{
|
||||
newLineWithCR = true;
|
||||
}
|
||||
else if (ch == '\n')
|
||||
{
|
||||
currentLine++;
|
||||
currentColumn = 0;
|
||||
}
|
||||
|
||||
int count = EnsureBytes(1);
|
||||
if (count == 0)
|
||||
{
|
||||
// end of stream
|
||||
return;
|
||||
}
|
||||
currentSbOffset++;
|
||||
|
||||
if (newLineWithCR && Lookahead() != '\n')
|
||||
{
|
||||
currentLine++;
|
||||
currentColumn = 0;
|
||||
}
|
||||
currentColumn++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (input != null)
|
||||
{
|
||||
input.Dispose();
|
||||
input = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current offset for the lexer input
|
||||
/// </summary>
|
||||
public int CurrentOffset
|
||||
{
|
||||
get { return bufferStartOffset + currentSbOffset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure correct number of bytes to buffer
|
||||
/// </summary>
|
||||
public int EnsureBytes(int bytesToBuffer)
|
||||
{
|
||||
if (currentSbOffset + bytesToBuffer > buffer.Length)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return buffer.Length - currentSbOffset;
|
||||
}
|
||||
int chArrayLength = bytesToBuffer - (buffer.Length - currentSbOffset) + 128;
|
||||
char[] chArray = new char[chArrayLength];
|
||||
int count = input.ReadBlock(chArray, 0, chArrayLength);
|
||||
buffer.Append(chArray, 0, count);
|
||||
if (count < chArrayLength)
|
||||
{
|
||||
input.Dispose();
|
||||
input = null;
|
||||
}
|
||||
return buffer.Length - currentSbOffset;
|
||||
}
|
||||
return bytesToBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// look ahead bytes in lexer input
|
||||
/// </summary>
|
||||
public char? Lookahead()
|
||||
{
|
||||
int count = EnsureBytes(1);
|
||||
if (count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return buffer[currentSbOffset];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// look ahead bytes in lexer input
|
||||
/// </summary>
|
||||
public char? Lookahead(int lookahead)
|
||||
{
|
||||
int count = EnsureBytes(lookahead + 1);
|
||||
if (count < lookahead + 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return buffer[currentSbOffset + lookahead];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush buffered text in lexer input
|
||||
/// </summary>
|
||||
public string FlushBufferedText()
|
||||
{
|
||||
string text;
|
||||
|
||||
text = buffer.ToString(0, currentSbOffset);
|
||||
bufferStartOffset += currentSbOffset;
|
||||
buffer.Remove(0, currentSbOffset);
|
||||
currentSbOffset = 0;
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public enum LexerTokenType
|
||||
{
|
||||
None,
|
||||
Text,
|
||||
TextVerbatim,
|
||||
Whitespace,
|
||||
NewLine,
|
||||
String,
|
||||
Eof,
|
||||
Error,
|
||||
Comment,
|
||||
|
||||
// batch commands
|
||||
Go,
|
||||
Reset,
|
||||
Ed,
|
||||
Execute,
|
||||
Quit,
|
||||
Exit,
|
||||
Include,
|
||||
Serverlist,
|
||||
Setvar,
|
||||
List,
|
||||
ErrorCommand,
|
||||
Out,
|
||||
Perftrace,
|
||||
Connect,
|
||||
OnError,
|
||||
Help,
|
||||
Xml,
|
||||
ListVar,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// This class gives information about lines being parsed by
|
||||
/// the Batch Parser
|
||||
/// </summary>
|
||||
public class LineInfo
|
||||
{
|
||||
private IEnumerable<Token> tokens;
|
||||
private IEnumerable<VariableReference> variableRefs;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for the LineInfo class
|
||||
/// </summary>
|
||||
public LineInfo(IEnumerable<Token> tokens, IEnumerable<VariableReference> variableRefs)
|
||||
{
|
||||
this.tokens = tokens;
|
||||
this.variableRefs = variableRefs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream position for offset and returns a PositionStruct
|
||||
/// </summary>
|
||||
public PositionStruct GetStreamPositionForOffset(int offset)
|
||||
{
|
||||
if (variableRefs != null)
|
||||
{
|
||||
offset = CalculateVarsUnresolvedOffset(offset);
|
||||
}
|
||||
int charCount = 0;
|
||||
Token lastToken = null;
|
||||
foreach (Token token in tokens)
|
||||
{
|
||||
lastToken = token;
|
||||
if (charCount + token.Text.Length > offset)
|
||||
{
|
||||
int line, column;
|
||||
CalculateLineColumnForOffset(token, offset - charCount, out line, out column);
|
||||
return new PositionStruct(line, column, token.Begin.Offset + (offset - charCount), token.Filename);
|
||||
}
|
||||
charCount += token.Text.Length;
|
||||
}
|
||||
if (lastToken != null)
|
||||
{
|
||||
return new PositionStruct(lastToken.End.Line, lastToken.End.Column, lastToken.End.Offset, lastToken.Filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PositionStruct(1, 1, 0, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CalculateLineColumnForOffset(Token token, int offset, out int line, out int column)
|
||||
{
|
||||
CalculateLineColumnForOffset(token.Text, offset, 0, token.Begin.Line, token.Begin.Column, out line, out column);
|
||||
}
|
||||
|
||||
internal static void CalculateLineColumnForOffset(string text, int offset,
|
||||
int offsetDelta, int lineDelta, int columnDelta, out int line, out int column)
|
||||
{
|
||||
line = lineDelta;
|
||||
column = columnDelta;
|
||||
int counter = offsetDelta;
|
||||
while (counter < offset)
|
||||
{
|
||||
bool newLineWithCR = false;
|
||||
|
||||
if (text[counter] == '\r')
|
||||
{
|
||||
newLineWithCR = true;
|
||||
}
|
||||
else if (text[counter] == '\n')
|
||||
{
|
||||
line++;
|
||||
column = 0;
|
||||
}
|
||||
counter++;
|
||||
if (newLineWithCR && counter < text.Length && text[counter] != '\n')
|
||||
{
|
||||
line++;
|
||||
column = 0;
|
||||
}
|
||||
column++;
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateVarsUnresolvedOffset(int offset)
|
||||
{
|
||||
// find offset of the beginning of variable substitution (if offset points to the middle of it)
|
||||
int diff = 0;
|
||||
foreach (VariableReference reference in variableRefs)
|
||||
{
|
||||
if (reference.Start >= offset)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (reference.VariableValue != null && offset < reference.Start + reference.VariableValue.Length)
|
||||
{
|
||||
offset = reference.Start;
|
||||
break;
|
||||
}
|
||||
if (reference.VariableValue != null)
|
||||
{
|
||||
diff += reference.Length - reference.VariableValue.Length;
|
||||
}
|
||||
}
|
||||
return offset + diff;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public enum OnErrorAction
|
||||
{
|
||||
Ignore = 0,
|
||||
Exit = 1,
|
||||
}
|
||||
}
|
||||
723
src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/Parser.cs
Normal file
723
src/Microsoft.SqlTools.ManagedBatchParser/BatchParser/Parser.cs
Normal file
@@ -0,0 +1,723 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ManagedBatchParser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// The Parser class on which the Batch Parser is based on
|
||||
/// </summary>
|
||||
public sealed class Parser : IDisposable
|
||||
{
|
||||
private readonly ICommandHandler commandHandler;
|
||||
private Lexer lexer;
|
||||
private List<Token> tokenBuffer;
|
||||
private readonly IVariableResolver variableResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the Parser class
|
||||
/// </summary>
|
||||
public Parser(ICommandHandler commandHandler, IVariableResolver variableResolver, TextReader reader, string name)
|
||||
{
|
||||
this.commandHandler = commandHandler;
|
||||
this.variableResolver = variableResolver;
|
||||
lexer = new Lexer(reader, name);
|
||||
tokenBuffer = new List<Token>();
|
||||
}
|
||||
|
||||
public bool ThrowOnUnresolvedVariable { get; set; }
|
||||
|
||||
private Token LookaheadToken
|
||||
{
|
||||
get { return lexer.CurrentToken; }
|
||||
}
|
||||
|
||||
private LexerTokenType LookaheadTokenType
|
||||
{
|
||||
get { return lexer.CurrentTokenType; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (lexer != null)
|
||||
{
|
||||
lexer.Dispose();
|
||||
lexer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AcceptWhitespaceOrComment()
|
||||
{
|
||||
bool found = false;
|
||||
while (LookaheadTokenType == LexerTokenType.Comment ||
|
||||
LookaheadTokenType == LexerTokenType.Whitespace)
|
||||
{
|
||||
Accept();
|
||||
found = true;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private bool Accept(LexerTokenType lexerTokenType)
|
||||
{
|
||||
if (LookaheadTokenType == lexerTokenType)
|
||||
{
|
||||
Accept();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddTokenToStringBuffer()
|
||||
{
|
||||
Token token = LookaheadToken;
|
||||
|
||||
if (token.TokenType != LexerTokenType.Comment)
|
||||
{
|
||||
// All variable references must be valid
|
||||
CheckVariableReferences(token);
|
||||
}
|
||||
if (token.TokenType == LexerTokenType.NewLine && token.Text.Length == 0)
|
||||
{
|
||||
// Add "virtual" token representing new line that was not in original file
|
||||
PositionStruct beginPos = token.Begin;
|
||||
PositionStruct endPos = new PositionStruct(beginPos.Line + 1, 1, beginPos.Offset, beginPos.Filename);
|
||||
|
||||
tokenBuffer.Add(new Token(LexerTokenType.NewLine, beginPos, endPos, Environment.NewLine, beginPos.Filename));
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenBuffer.Add(token);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckVariableReferences(Token token)
|
||||
{
|
||||
// AddVariableReferences will raise error if reference is not constructed correctly
|
||||
AddVariableReferences(token, 0, null);
|
||||
}
|
||||
|
||||
private void AddVariableReferences(Token token, int offset, IList<VariableReference> variableRefs)
|
||||
{
|
||||
if (lexer.RecognizeSqlCmdSyntax == false)
|
||||
{
|
||||
// variables are recognized only in sqlcmd mode.
|
||||
return;
|
||||
}
|
||||
|
||||
string text = token.Text;
|
||||
|
||||
int i = 0;
|
||||
int startIndex = -1;
|
||||
int state = 0;
|
||||
while (i < text.Length)
|
||||
{
|
||||
char ch = text[i];
|
||||
if (state == 0 && ch == '$')
|
||||
{
|
||||
state = 1;
|
||||
}
|
||||
else if (state == 1)
|
||||
{
|
||||
if (ch == '(')
|
||||
{
|
||||
state = 2;
|
||||
}
|
||||
else if (ch != '$')
|
||||
{
|
||||
state = 0;
|
||||
}
|
||||
}
|
||||
else if (state == 2)
|
||||
{
|
||||
if (Lexer.IsStartIdentifierChar(ch))
|
||||
{
|
||||
startIndex = i;
|
||||
state = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseError(ErrorCode.InvalidVariableName, GetSubToken(token, i - 2, i + 1));
|
||||
}
|
||||
}
|
||||
else if (state == 3)
|
||||
{
|
||||
if (ch == ')')
|
||||
{
|
||||
if (variableRefs != null)
|
||||
{
|
||||
variableRefs.Add(new VariableReference(offset + startIndex - 2, i - startIndex + 3, text.Substring(startIndex, i - startIndex)));
|
||||
}
|
||||
state = 0;
|
||||
}
|
||||
else if (Lexer.IsIdentifierChar(ch) == false)
|
||||
{
|
||||
RaiseError(ErrorCode.InvalidVariableName, GetSubToken(token, startIndex - 2, i + 1));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (state == 2 || state == 3)
|
||||
{
|
||||
RaiseError(ErrorCode.InvalidVariableName, GetSubToken(token, startIndex - 2, i));
|
||||
}
|
||||
}
|
||||
|
||||
private static Token GetSubToken(Token token, int startOffset, int endOffset, LexerTokenType? newTokenType = null)
|
||||
{
|
||||
LexerTokenType tokenType = newTokenType.HasValue ? newTokenType.Value : token.TokenType;
|
||||
string text = token.Text.Substring(startOffset, endOffset - startOffset);
|
||||
string filename = token.Begin.Filename;
|
||||
PositionStruct beginPos, endPos;
|
||||
|
||||
int beginLine, beginColumn;
|
||||
LineInfo.CalculateLineColumnForOffset(token, startOffset, out beginLine, out beginColumn);
|
||||
beginPos = new PositionStruct(beginLine, beginColumn, token.Begin.Offset + startOffset, filename);
|
||||
|
||||
int endLine, endColumn;
|
||||
LineInfo.CalculateLineColumnForOffset(token, endOffset, out endLine, out endColumn);
|
||||
endPos = new PositionStruct(endLine, endColumn, token.Begin.Offset + endOffset, filename);
|
||||
|
||||
return new Token(tokenType, beginPos, endPos, text, filename);
|
||||
}
|
||||
|
||||
private LexerTokenType Accept()
|
||||
{
|
||||
lexer.ConsumeToken();
|
||||
return LookaheadTokenType;
|
||||
}
|
||||
|
||||
private void ExecuteBatch(int repeatCount)
|
||||
{
|
||||
BatchParserAction action;
|
||||
action = commandHandler.Go(new TextBlock(this, tokenBuffer), repeatCount);
|
||||
|
||||
if (action == BatchParserAction.Abort)
|
||||
{
|
||||
RaiseError(ErrorCode.Aborted);
|
||||
}
|
||||
tokenBuffer = new List<Token>();
|
||||
}
|
||||
|
||||
private bool Expect(LexerTokenType lexerTokenType)
|
||||
{
|
||||
if (LookaheadTokenType == lexerTokenType)
|
||||
return true;
|
||||
RaiseError(ErrorCode.TokenExpected);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ExpectAndAccept(LexerTokenType lexerTokenType)
|
||||
{
|
||||
if (Accept(lexerTokenType))
|
||||
return true;
|
||||
RaiseError(ErrorCode.TokenExpected);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Parse()
|
||||
{
|
||||
Accept();
|
||||
ParseLines();
|
||||
}
|
||||
|
||||
private void ParseGo()
|
||||
{
|
||||
int repeatCount = 1;
|
||||
AcceptWhitespaceOrComment();
|
||||
if (LookaheadTokenType == LexerTokenType.Text)
|
||||
{
|
||||
string text = ResolveVariables(LookaheadToken, 0, null);
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (Char.IsDigit(text[i]) == false)
|
||||
{
|
||||
RaiseError(ErrorCode.InvalidNumber);
|
||||
}
|
||||
}
|
||||
bool result = Int32.TryParse(text, out repeatCount);
|
||||
if (result == false)
|
||||
{
|
||||
RaiseError(ErrorCode.InvalidNumber);
|
||||
}
|
||||
Accept(LexerTokenType.Text);
|
||||
AcceptWhitespaceOrComment();
|
||||
}
|
||||
if (LookaheadTokenType != LexerTokenType.Eof)
|
||||
{
|
||||
ExpectAndAccept(LexerTokenType.NewLine);
|
||||
}
|
||||
ExecuteBatch(repeatCount);
|
||||
}
|
||||
|
||||
private void ParseInclude()
|
||||
{
|
||||
BatchParserAction action;
|
||||
|
||||
Accept(LexerTokenType.Whitespace);
|
||||
Expect(LexerTokenType.Text);
|
||||
|
||||
Token token = LookaheadToken;
|
||||
|
||||
if (token.Text.StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
RaiseError(ErrorCode.TokenExpected);
|
||||
}
|
||||
|
||||
lexer.SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment | TextRuleFlags.RecognizeDoubleQuotedString);
|
||||
Accept();
|
||||
AcceptWhitespaceOrComment();
|
||||
if (LookaheadTokenType != LexerTokenType.Eof)
|
||||
{
|
||||
Expect(LexerTokenType.NewLine);
|
||||
}
|
||||
TextReader textReader;
|
||||
string newFilename;
|
||||
|
||||
string tokenText = token.Text;
|
||||
IEnumerable<Token> tokens;
|
||||
if (tokenText.IndexOf('"') != -1)
|
||||
{
|
||||
tokens = SplitQuotedTextToken(token);
|
||||
}
|
||||
else
|
||||
{
|
||||
tokens = new[] { token };
|
||||
}
|
||||
|
||||
action = commandHandler.Include(new TextBlock(this, tokens), out textReader, out newFilename);
|
||||
|
||||
if (action == BatchParserAction.Abort)
|
||||
{
|
||||
RaiseError(ErrorCode.Aborted);
|
||||
}
|
||||
if (textReader != null)
|
||||
{
|
||||
this.PushInput(textReader, newFilename);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Token> SplitQuotedTextToken(Token token)
|
||||
{
|
||||
string tokenText = token.Text;
|
||||
|
||||
if (tokenText.Length <= 1)
|
||||
{
|
||||
return new[] { token };
|
||||
}
|
||||
|
||||
IList<Token> tokens = new List<Token>();
|
||||
|
||||
int offset = 0;
|
||||
int quotePos = tokenText.IndexOf('"');
|
||||
while (quotePos != -1)
|
||||
{
|
||||
int closingQuotePos;
|
||||
if (quotePos != offset)
|
||||
{
|
||||
tokens.Add(GetSubToken(token, offset, quotePos));
|
||||
offset = quotePos;
|
||||
}
|
||||
closingQuotePos = tokenText.IndexOf('"', quotePos + 1);
|
||||
if (closingQuotePos == -1)
|
||||
{
|
||||
// odd number of " characters
|
||||
break;
|
||||
}
|
||||
if (closingQuotePos + 1 < tokenText.Length && tokenText[closingQuotePos + 1] == '"')
|
||||
{
|
||||
// two consequtive " characters: report one of them
|
||||
tokens.Add(GetSubToken(token, quotePos + 1, closingQuotePos + 1, LexerTokenType.TextVerbatim));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the contents of the quoted string, but remove the " characters
|
||||
tokens.Add(GetSubToken(token, quotePos + 1, closingQuotePos, LexerTokenType.TextVerbatim));
|
||||
}
|
||||
offset = closingQuotePos + 1;
|
||||
|
||||
quotePos = tokenText.IndexOf('"', offset);
|
||||
}
|
||||
if (offset != tokenText.Length)
|
||||
{
|
||||
tokens.Add(GetSubToken(token, offset, tokenText.Length));
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
internal void PushInput(TextReader reader, string filename)
|
||||
{
|
||||
lexer.PushInput(reader, filename);
|
||||
}
|
||||
|
||||
private void ParseLines()
|
||||
{
|
||||
do
|
||||
{
|
||||
LexerTokenType tokenType = LookaheadTokenType;
|
||||
switch (tokenType)
|
||||
{
|
||||
case LexerTokenType.OnError:
|
||||
RemoveLastWhitespaceToken();
|
||||
Token onErrorToken = LookaheadToken;
|
||||
Accept();
|
||||
ParseOnErrorCommand(onErrorToken);
|
||||
break;
|
||||
case LexerTokenType.Eof:
|
||||
if (tokenBuffer.Count > 0)
|
||||
{
|
||||
ExecuteBatch(1);
|
||||
}
|
||||
return;
|
||||
case LexerTokenType.Go:
|
||||
RemoveLastWhitespaceToken();
|
||||
Accept();
|
||||
ParseGo();
|
||||
break;
|
||||
case LexerTokenType.Include:
|
||||
RemoveLastWhitespaceToken();
|
||||
Accept();
|
||||
ParseInclude();
|
||||
break;
|
||||
case LexerTokenType.Comment:
|
||||
case LexerTokenType.NewLine:
|
||||
case LexerTokenType.Text:
|
||||
case LexerTokenType.Whitespace:
|
||||
AddTokenToStringBuffer();
|
||||
Accept();
|
||||
break;
|
||||
case LexerTokenType.Setvar:
|
||||
Token setvarToken = LookaheadToken;
|
||||
RemoveLastWhitespaceToken();
|
||||
Accept();
|
||||
ParseSetvar(setvarToken);
|
||||
break;
|
||||
case LexerTokenType.Connect:
|
||||
case LexerTokenType.Ed:
|
||||
case LexerTokenType.ErrorCommand:
|
||||
case LexerTokenType.Execute:
|
||||
case LexerTokenType.Exit:
|
||||
case LexerTokenType.Help:
|
||||
case LexerTokenType.List:
|
||||
case LexerTokenType.ListVar:
|
||||
case LexerTokenType.Out:
|
||||
case LexerTokenType.Perftrace:
|
||||
case LexerTokenType.Quit:
|
||||
case LexerTokenType.Reset:
|
||||
case LexerTokenType.Serverlist:
|
||||
case LexerTokenType.Xml:
|
||||
RaiseError(ErrorCode.UnsupportedCommand,
|
||||
string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionError_CommandNotSupported, tokenType));
|
||||
break;
|
||||
default:
|
||||
RaiseError(ErrorCode.UnrecognizedToken);
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
private void RemoveLastWhitespaceToken()
|
||||
{
|
||||
if (tokenBuffer.Count > 0 && tokenBuffer[tokenBuffer.Count - 1].TokenType == LexerTokenType.Whitespace)
|
||||
{
|
||||
tokenBuffer.RemoveAt(tokenBuffer.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseOnErrorCommand(Token onErrorToken)
|
||||
{
|
||||
ExpectAndAccept(LexerTokenType.Whitespace);
|
||||
Expect(LexerTokenType.Text);
|
||||
|
||||
string action;
|
||||
action = ResolveVariables(LookaheadToken, 0, null);
|
||||
|
||||
OnErrorAction onErrorAction;
|
||||
|
||||
if (action.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
onErrorAction = OnErrorAction.Exit;
|
||||
}
|
||||
else if (action.Equals("ignore", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
onErrorAction = OnErrorAction.Ignore;
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseError(ErrorCode.UnrecognizedToken);
|
||||
return;
|
||||
}
|
||||
|
||||
Accept(LexerTokenType.Text);
|
||||
AcceptWhitespaceOrComment();
|
||||
if (LookaheadTokenType != LexerTokenType.Eof)
|
||||
{
|
||||
Expect(LexerTokenType.NewLine);
|
||||
}
|
||||
|
||||
BatchParserAction parserAction;
|
||||
|
||||
parserAction = commandHandler.OnError(onErrorToken, onErrorAction);
|
||||
|
||||
if (parserAction == BatchParserAction.Abort)
|
||||
{
|
||||
RaiseError(ErrorCode.Aborted);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseSetvar(Token setvarToken)
|
||||
{
|
||||
string variableName;
|
||||
string variableValue = null;
|
||||
Accept(LexerTokenType.Whitespace);
|
||||
Expect(LexerTokenType.Text);
|
||||
|
||||
variableName = LookaheadToken.Text;
|
||||
|
||||
Accept();
|
||||
Accept(LexerTokenType.Whitespace);
|
||||
|
||||
switch (LookaheadTokenType)
|
||||
{
|
||||
case LexerTokenType.Text:
|
||||
// No variable substitution
|
||||
variableValue = UnquoteVariableValue(LookaheadToken.Text);
|
||||
lexer.SetTextState(TextRuleFlags.ReportWhitespace | TextRuleFlags.RecognizeLineComment | TextRuleFlags.RecognizeDoubleQuotedString);
|
||||
Accept();
|
||||
AcceptWhitespaceOrComment();
|
||||
|
||||
if (LookaheadTokenType != LexerTokenType.NewLine &&
|
||||
LookaheadTokenType != LexerTokenType.Eof)
|
||||
{
|
||||
RaiseError(ErrorCode.UnrecognizedToken);
|
||||
}
|
||||
|
||||
if (LookaheadTokenType != LexerTokenType.Eof)
|
||||
{
|
||||
Accept();
|
||||
}
|
||||
break;
|
||||
case LexerTokenType.NewLine:
|
||||
case LexerTokenType.Eof:
|
||||
Accept();
|
||||
break;
|
||||
default:
|
||||
RaiseError(ErrorCode.UnrecognizedToken);
|
||||
break;
|
||||
}
|
||||
|
||||
variableResolver.SetVariable(setvarToken.Begin, variableName, variableValue);
|
||||
}
|
||||
|
||||
internal void RaiseError(ErrorCode errorCode, string message = null)
|
||||
{
|
||||
RaiseError(errorCode, LookaheadToken, message);
|
||||
}
|
||||
|
||||
internal static void RaiseError(ErrorCode errorCode, Token token, string message = null)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
message = string.Format(CultureInfo.CurrentCulture, SR.BatchParser_IncorrectSyntax, token.Text);
|
||||
}
|
||||
throw new BatchParserException(errorCode, token, message);
|
||||
}
|
||||
|
||||
internal string ResolveVariables(Token inputToken, int offset, List<VariableReference> variableRefs)
|
||||
{
|
||||
List<VariableReference> variableRefsLocal = new List<VariableReference>();
|
||||
AddVariableReferences(inputToken, offset, variableRefsLocal);
|
||||
|
||||
string inputString = inputToken.Text;
|
||||
if (variableRefsLocal.Count == 0)
|
||||
{
|
||||
// no variable references to substitute
|
||||
return inputString;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int lastChar = 0;
|
||||
PositionStruct? variablePos = null;
|
||||
foreach (VariableReference reference in variableRefsLocal)
|
||||
{
|
||||
int line;
|
||||
int column;
|
||||
|
||||
if (variablePos != null)
|
||||
{
|
||||
LineInfo.CalculateLineColumnForOffset(inputToken.Text, reference.Start - offset,
|
||||
variablePos.Value.Offset, variablePos.Value.Line, variablePos.Value.Column,
|
||||
out line, out column);
|
||||
}
|
||||
else
|
||||
{
|
||||
LineInfo.CalculateLineColumnForOffset(inputToken, reference.Start - offset, out line, out column);
|
||||
}
|
||||
|
||||
variablePos = new PositionStruct(
|
||||
line: line,
|
||||
column: column,
|
||||
offset: reference.Start - offset,
|
||||
filename: inputToken.Filename);
|
||||
string value = variableResolver.GetVariable(variablePos.Value, reference.VariableName);
|
||||
if (value == null)
|
||||
{
|
||||
// Undefined variable
|
||||
if (ThrowOnUnresolvedVariable == true)
|
||||
{
|
||||
RaiseError(ErrorCode.VariableNotDefined, inputToken,
|
||||
string.Format(CultureInfo.CurrentCulture, SR.BatchParser_VariableNotDefined, reference.VariableName));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
reference.VariableValue = value;
|
||||
sb.Append(inputToken.Text, lastChar, reference.Start - offset - lastChar);
|
||||
sb.Append(value);
|
||||
lastChar = reference.Start - offset + reference.Length;
|
||||
}
|
||||
if (lastChar != inputString.Length)
|
||||
{
|
||||
sb.Append(inputString, lastChar, inputString.Length - lastChar);
|
||||
}
|
||||
if (variableRefs != null)
|
||||
{
|
||||
variableRefs.AddRange(variableRefsLocal);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string UnquoteVariableValue(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s) == false)
|
||||
{
|
||||
if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == s[0])
|
||||
{
|
||||
// all " characters must be doubled
|
||||
for (int i = 1; i < s.Length - 1; i++)
|
||||
{
|
||||
if (s[i] == '"')
|
||||
{
|
||||
if (i + 1 >= s.Length - 1 || s[i + 1] != '"')
|
||||
{
|
||||
RaiseError(ErrorCode.TokenExpected);
|
||||
}
|
||||
// skip next character
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return s.Substring(1, s.Length - 2).Replace("\"\"", "\"");
|
||||
}
|
||||
else if (s[0] == '"' || s[s.Length - 1] == '"')
|
||||
{
|
||||
RaiseError(ErrorCode.TokenExpected);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s.Length >= 2 && s[0] == '-' && s[1] == '-')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (s.IndexOf('"') >= 0)
|
||||
{
|
||||
RaiseError(ErrorCode.TokenExpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public void SetRecognizeSqlCmdSyntax(bool recognizeSqlCmdSyntax)
|
||||
{
|
||||
lexer.RecognizeSqlCmdSyntax = recognizeSqlCmdSyntax;
|
||||
}
|
||||
|
||||
public static TextReader GetBufferedTextReaderForFile(string filename)
|
||||
{
|
||||
Exception handledException = null;
|
||||
try
|
||||
{
|
||||
return new StringReader(File.ReadAllText(filename));
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
handledException = exception;
|
||||
}
|
||||
catch (NotSupportedException exception)
|
||||
{
|
||||
handledException = exception;
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
handledException = exception;
|
||||
}
|
||||
catch (SecurityException exception)
|
||||
{
|
||||
handledException = exception;
|
||||
}
|
||||
|
||||
if (null != handledException)
|
||||
{
|
||||
throw new Exception(handledException.Message, handledException);
|
||||
}
|
||||
|
||||
Debug.Fail("This code should not be reached.");
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void SetBatchDelimiter(string batchSeparator)
|
||||
{
|
||||
// Not implemeneted as only GO is supported now
|
||||
}
|
||||
|
||||
public static string TokenTypeToCommandString(LexerTokenType lexerTokenType)
|
||||
{
|
||||
switch (lexerTokenType)
|
||||
{
|
||||
case LexerTokenType.Connect:
|
||||
return "Connect";
|
||||
case LexerTokenType.Ed:
|
||||
return "Ed";
|
||||
case LexerTokenType.ErrorCommand:
|
||||
return "Error";
|
||||
case LexerTokenType.Execute:
|
||||
return "!!";
|
||||
case LexerTokenType.Exit:
|
||||
return "Exit";
|
||||
case LexerTokenType.Help:
|
||||
return "Help";
|
||||
case LexerTokenType.List:
|
||||
return "List";
|
||||
case LexerTokenType.ListVar:
|
||||
return "ListVar";
|
||||
case LexerTokenType.OnError:
|
||||
return "On Error";
|
||||
case LexerTokenType.Out:
|
||||
return "Out";
|
||||
case LexerTokenType.Perftrace:
|
||||
return "PerfTrace";
|
||||
case LexerTokenType.Quit:
|
||||
return "Quit";
|
||||
case LexerTokenType.Reset:
|
||||
return "Reset";
|
||||
case LexerTokenType.Serverlist:
|
||||
return "ServerList";
|
||||
case LexerTokenType.Xml:
|
||||
return "Xml";
|
||||
default:
|
||||
Debug.Fail("Unknown batch parser command");
|
||||
return lexerTokenType.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
[Serializable]
|
||||
public struct PositionStruct
|
||||
{
|
||||
private readonly int line;
|
||||
private readonly int column;
|
||||
private readonly int offset;
|
||||
private readonly string filename;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the PositionStruct class
|
||||
/// </summary>
|
||||
public PositionStruct(int line, int column, int offset, string filename)
|
||||
{
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
this.offset = offset;
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get line from the PositionStruct
|
||||
/// </summary>
|
||||
public int Line { get { return line; } }
|
||||
|
||||
/// <summary>
|
||||
/// Get column from the PositionStruct
|
||||
/// </summary>
|
||||
public int Column { get { return column; } }
|
||||
|
||||
/// <summary>
|
||||
/// Get offset from the PositionStruct
|
||||
/// </summary>
|
||||
public int Offset { get { return offset; } }
|
||||
|
||||
/// <summary>
|
||||
/// Get file name from the PositionStruct
|
||||
/// </summary>
|
||||
public string Filename { get { return filename; } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public class TextBlock
|
||||
{
|
||||
private readonly Parser parser;
|
||||
private readonly IEnumerable<Token> tokens;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the TextBlock class
|
||||
/// </summary>
|
||||
public TextBlock(Parser parser, Token token) : this(parser, new[] { token })
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the TextBlock class
|
||||
/// </summary>
|
||||
public TextBlock(Parser parser, IEnumerable<Token> tokens)
|
||||
{
|
||||
this.parser = parser;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text from TextBlock
|
||||
/// </summary>
|
||||
public void GetText(bool resolveVariables, out string text, out LineInfo lineInfo)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<VariableReference> variableRefs = null;
|
||||
|
||||
if (resolveVariables == false)
|
||||
{
|
||||
foreach (Token token in tokens)
|
||||
{
|
||||
sb.Append(token.Text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
variableRefs = new List<VariableReference>();
|
||||
foreach (Token token in tokens)
|
||||
{
|
||||
if (token.TokenType == LexerTokenType.Text)
|
||||
{
|
||||
sb.Append(parser.ResolveVariables(token, sb.Length, variableRefs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// comments and whitespaces do not need variable expansion
|
||||
sb.Append(token.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
lineInfo = new LineInfo(tokens, variableRefs);
|
||||
text = sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
[Flags]
|
||||
internal enum TextRuleFlags
|
||||
{
|
||||
ReportWhitespace = 1,
|
||||
RecognizeDoubleQuotedString = 2,
|
||||
RecognizeSingleQuotedString = 4,
|
||||
RecognizeLineComment = 8,
|
||||
RecognizeBlockComment = 16,
|
||||
RecognizeBrace = 32,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
public sealed class Token
|
||||
{
|
||||
/// <summary>
|
||||
/// Token class used by the lexer in Batch Parser
|
||||
/// </summary>
|
||||
internal Token(LexerTokenType tokenType, PositionStruct begin, PositionStruct end, string text, string filename)
|
||||
{
|
||||
TokenType = tokenType;
|
||||
Begin = begin;
|
||||
End = end;
|
||||
Text = text;
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file name associated with Token
|
||||
/// </summary>
|
||||
public string Filename { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get beginning position for the Token
|
||||
/// </summary>
|
||||
public PositionStruct Begin { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get end position for the Token
|
||||
/// </summary>
|
||||
public PositionStruct End { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get text assocaited with the Token
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get token type of the Token
|
||||
/// </summary>
|
||||
public LexerTokenType TokenType { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.BatchParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for reference of variables used by the lexer
|
||||
/// </summary>
|
||||
public sealed class VariableReference
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor method for VariableReference class
|
||||
/// </summary>
|
||||
public VariableReference(int start, int length, string variableName)
|
||||
{
|
||||
Start = start;
|
||||
Length = length;
|
||||
VariableName = variableName;
|
||||
VariableValue = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get length associated with the VariableReference
|
||||
/// </summary>
|
||||
public int Length { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get start position associated with the VariableReference
|
||||
/// </summary>
|
||||
public int Start { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get variable name associated with the VariableReference
|
||||
/// </summary>
|
||||
public string VariableName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get variable value associated with the VariableReference
|
||||
/// </summary>
|
||||
public string VariableValue { get; internal set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user