Invoke-SqlCmd handles ":!!if" in inconsistent way (PS5 vs PS6) (#806)

* Invoke-SqlCmd handles ":!!if" in inconsistent way (PS5 vs PS6)

Fix: Set wordBounday to false for "!!" in Lexer.cs

* Issue : Invoke-SqlCmd handles ":!!if" in inconsistent way (PS5 vs PS6)
TFS: http://sqlbuvsts01:8080/Main/SQL%20Server/_workitems#id=12817630&triage=true&_a=edit
Fix:
1) Wrote two test cases in BatchParserTests.cs
This commit is contained in:
NiranjanVirtuosity
2019-05-14 03:39:21 +05:30
committed by Karl Burtram
parent 5392f81d54
commit 2e2b764c6d
2 changed files with 142 additions and 90 deletions

View File

@@ -43,8 +43,9 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser
/// </summary> /// </summary>
public Token CurrentToken public Token CurrentToken
{ {
get { get
return currentToken; {
return currentToken;
} }
} }
@@ -83,7 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser
if (CurrentTokenType == LexerTokenType.Eof) if (CurrentTokenType == LexerTokenType.Eof)
{ {
popInputAtNextConsume = true; popInputAtNextConsume = true;
if(inputStack.Count > 0) if (inputStack.Count > 0)
{ {
// report as empty NewLine token // report as empty NewLine token
currentToken = new Token( currentToken = new Token(
@@ -473,7 +474,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser
SetToken(LexerTokenType.Eof); SetToken(LexerTokenType.Eof);
return true; return true;
} }
if (ch.HasValue) if (ch.HasValue)
{ {
if (IsNewLineChar(ch.Value)) if (IsNewLineChar(ch.Value))
@@ -600,10 +601,10 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser
string text = currentInput.FlushBufferedText(); string text = currentInput.FlushBufferedText();
currentToken = new Token( currentToken = new Token(
lexerTokenType, lexerTokenType,
tokenBeginPosition, tokenBeginPosition,
new PositionStruct(currentInput.CurrentLine, currentInput.CurrentColumn, currentInput.CurrentOffset, currentInput.Filename), new PositionStruct(currentInput.CurrentLine, currentInput.CurrentColumn, currentInput.CurrentOffset, currentInput.Filename),
text, text,
currentInput.Filename); currentInput.Filename);
} }
@@ -625,7 +626,7 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser
if (wordBoundary) if (wordBoundary)
{ {
char? ch = Lookahead(text.Length); char? ch = Lookahead(text.Length);
if (ch != null && IsWhitespaceChar(ch.Value) == false && IsNewLineChar(ch.Value) == false if (ch != null && IsWhitespaceChar(ch.Value) == false && IsNewLineChar(ch.Value) == false
&& ch != '$' && ch != '/' && ch != '-' && ch != '\'' && ch != '"' && ch != '(' && ch != '[' && ch != '!') && ch != '$' && ch != '/' && ch != '-' && ch != '\'' && ch != '"' && ch != '(' && ch != '[' && ch != '!')
{ {
return false; return false;
@@ -645,91 +646,108 @@ namespace Microsoft.SqlTools.ServiceLayer.BatchParser
{ {
Consume(); // colon Consume(); // colon
if (TryAccept("reset", true)) /*
{ TryAccept(text: "!!", wordBoundary: false)
SetToken(LexerTokenType.Reset); ----------------------
return true; Reason:
} * The behavior is differ from SqlCmd and current implementation.
else if (TryAccept("ed", true)) * Example:
{ * !!Dir (without whitespace)
SetToken(LexerTokenType.Ed); * Command: Invoke-Sqlcmd -Query ':!!if exist foo.txt del foo.txt'
return true; * Result : Incorrect syntax near ':'. (InCorrect)
}
else if (TryAccept("!!", true)) * !! DIR (with whitespace)
* Command: Invoke-Sqlcmd -Query ':!! if exist foo.txt del foo.txt'
* Result : Command Execute is not supported. (Correct)
* TryAccept return false , if we pass true to parameter wordBoundary and LexerTokenType will set to Text instead of Execute.
* So by passing wordBoundary as false, I will get TryAccept as true and LexerTokenType will set to Execute.
*/
if (TryAccept(text: "!!", wordBoundary: false))
{ {
SetToken(LexerTokenType.Execute); SetToken(LexerTokenType.Execute);
return true; return true;
} }
else if (TryAccept("quit", true)) else if (TryAccept(text: "connect", wordBoundary: 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); SetToken(LexerTokenType.Connect);
return true; return true;
} }
else if (TryAccept("on error", true)) else if (TryAccept(text: "ed", wordBoundary: true))
{ {
SetToken(LexerTokenType.OnError); SetToken(LexerTokenType.Ed);
return true; return true;
} }
else if (TryAccept("help", true)) else if (TryAccept(text: "error", wordBoundary: true))
{
SetToken(LexerTokenType.ErrorCommand);
return true;
}
else if (TryAccept(text: "exit", wordBoundary: true))
{
SetToken(LexerTokenType.Exit);
return true;
}
else if (TryAccept(text: "help", wordBoundary: true))
{ {
SetToken(LexerTokenType.Help); SetToken(LexerTokenType.Help);
return true; return true;
} }
else if (TryAccept("xml", true)) else if (TryAccept(text: "list", wordBoundary: true))
{ {
SetToken(LexerTokenType.Xml); SetToken(LexerTokenType.List);
return true; return true;
} }
else if (TryAccept("listvar", true)) else if (TryAccept(text: "listvar", wordBoundary: true))
{ {
SetToken(LexerTokenType.ListVar); SetToken(LexerTokenType.ListVar);
return true; return true;
} }
else if (TryAccept(text: "on error", wordBoundary: true))
{
SetToken(LexerTokenType.OnError);
return true;
}
else if (TryAccept(text: "out", wordBoundary: true))
{
SetToken(LexerTokenType.Out);
return true;
}
else if (TryAccept(text: "perftrace", wordBoundary: true))
{
SetToken(LexerTokenType.Perftrace);
return true;
}
else if (TryAccept(text: "quit", wordBoundary: true))
{
SetToken(LexerTokenType.Quit);
return true;
}
else if (TryAccept(text: "r", wordBoundary: true))
{
SetToken(LexerTokenType.Include);
return true;
}
else if (TryAccept(text: "reset", wordBoundary: true))
{
SetToken(LexerTokenType.Reset);
return true;
}
else if (TryAccept(text: "serverlist", wordBoundary: true))
{
SetToken(LexerTokenType.Serverlist);
return true;
}
else if (TryAccept(text: "setvar", wordBoundary: true))
{
SetToken(LexerTokenType.Setvar);
return true;
}
else if (TryAccept(text: "xml", wordBoundary: true))
{
SetToken(LexerTokenType.Xml);
return true;
}
return false; return false;
} }

View File

@@ -3,17 +3,17 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.TSQLExecutionEngine; using Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.TSQLExecutionEngine;
using Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.Utility; using Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.Utility;
using Microsoft.SqlTools.ServiceLayer.BatchParser; using Microsoft.SqlTools.ServiceLayer.BatchParser;
using Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode; using Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode;
using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.Test.Common;
using Microsoft.SqlTools.ServiceLayer.Test.Common.Baselined; using Microsoft.SqlTools.ServiceLayer.Test.Common.Baselined;
using System;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Text;
using Xunit; using Xunit;
namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
@@ -122,7 +122,6 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
} }
} }
// Verify the execution by passing long value , Except a exception. // Verify the execution by passing long value , Except a exception.
[Fact] [Fact]
public void VerifyInvalidNumber() public void VerifyInvalidNumber()
@@ -140,8 +139,8 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
{ {
p.ThrowOnUnresolvedVariable = true; p.ThrowOnUnresolvedVariable = true;
handler.SetParser(p); handler.SetParser(p);
// This test will fail because we are passing invalid number. // This test will fail because we are passing invalid number.
// Exception will be raised from ParseGo() // Exception will be raised from ParseGo()
Assert.Throws<BatchParserException>(() => p.Parse()); Assert.Throws<BatchParserException>(() => p.Parse());
} }
} }
@@ -223,7 +222,6 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
{ {
try try
{ {
string query = ":SETVAR a 10"; string query = ":SETVAR a 10";
var inputStream = GenerateStreamFromString(query); var inputStream = GenerateStreamFromString(query);
using (Lexer lexer = new Lexer(new StreamReader(inputStream), "Test.sql")) using (Lexer lexer = new Lexer(new StreamReader(inputStream), "Test.sql"))
@@ -232,15 +230,52 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
} }
executionResult = ScriptExecutionResult.Success; executionResult = ScriptExecutionResult.Success;
} }
catch (Exception ex) catch (Exception)
{ {
executionResult = ScriptExecutionResult.Failure; executionResult = ScriptExecutionResult.Failure;
} }
// we doesn't expect any exception or testCase failures // we doesn't expect any exception or testCase failures
Assert.Equal<ScriptExecutionResult>(ScriptExecutionResult.Success, executionResult); Assert.Equal<ScriptExecutionResult>(ScriptExecutionResult.Success, executionResult);
}
// This test case is to verify that, Powershell's Invoke-SqlCmd handles ":!!if" in an inconsistent way
// Inconsistent way means, instead of throwing an exception as "Command Execute is not supported." it was throwing "Incorrect syntax near ':'."
[Fact]
public void VerifySqlCmdExecute()
{
string query = ":!!if exist foo.txt del foo.txt";
var inputStream = GenerateStreamFromString(query);
TestCommandHandler handler = new TestCommandHandler(new StringBuilder());
IVariableResolver resolver = new TestVariableResolver(new StringBuilder());
using (Parser p = new Parser(
handler,
resolver,
new StringReader(query),
"test"))
{
p.ThrowOnUnresolvedVariable = true;
handler.SetParser(p);
var exception = Assert.Throws<BatchParserException>(() => p.Parse());
// Verify the message should be "Command Execute is not supported."
Assert.Equal("Command Execute is not supported.", exception.Message);
}
}
// This test case is to verify that, Lexer type for :!!If was set to "Text" instead of "Execute"
[Fact]
public void VerifyLexerTypeOfSqlCmdIFisExecute()
{
string query = ":!!if exist foo.txt del foo.txt";
var inputStream = GenerateStreamFromString(query);
LexerTokenType type = LexerTokenType.None;
using (Lexer lexer = new Lexer(new StreamReader(inputStream), "Test.sql"))
{
lexer.ConsumeToken();
type = lexer.CurrentTokenType;
}
// we are expecting the lexer type should to be Execute.
Assert.Equal("Execute", type.ToString());
} }
// Verify the custom exception functionality by raising user defined error. // Verify the custom exception functionality by raising user defined error.
@@ -271,7 +306,7 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
{ {
using (ExecutionEngine executionEngine = new ExecutionEngine()) using (ExecutionEngine executionEngine = new ExecutionEngine())
{ {
string query = @"SELECT 1+2 string query = @"SELECT 1+2
Go 2"; Go 2";
using (SqlConnection con = new SqlConnection(CONNECTION_STRING)) using (SqlConnection con = new SqlConnection(CONNECTION_STRING))
{ {
@@ -287,7 +322,7 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
} }
// Verify whether the batchParser execute SqlCmd. // Verify whether the batchParser execute SqlCmd.
//[Fact] // This Testcase should execute and pass, But it is failing now. //[Fact] // This Testcase should execute and pass, But it is failing now.
public void VerifyIsSqlCmd() public void VerifyIsSqlCmd()
{ {
using (ExecutionEngine executionEngine = new ExecutionEngine()) using (ExecutionEngine executionEngine = new ExecutionEngine())
@@ -299,11 +334,10 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
TestExecutor testExecutor = new TestExecutor(query, con, new ExecutionEngineConditions()); TestExecutor testExecutor = new TestExecutor(query, con, new ExecutionEngineConditions());
testExecutor.Run(); testExecutor.Run();
Assert.True(testExecutor.ResultCountQueue.Count >= 1); Assert.True(testExecutor.ResultCountQueue.Count >= 1);
} }
} }
} }
// Verify whether the executionEngine execute Batch // Verify whether the executionEngine execute Batch
[Fact] [Fact]
public void VerifyExecuteBatch() public void VerifyExecuteBatch()
@@ -318,11 +352,11 @@ namespace Microsoft.SqlTools.ManagedBatchParser.UnitTests.BatchParser
executionEngine.BatchParserExecutionFinished += OnBatchParserExecutionFinished; executionEngine.BatchParserExecutionFinished += OnBatchParserExecutionFinished;
executionEngine.ExecuteBatch(new ScriptExecutionArgs(query, con, 15, new ExecutionEngineConditions(), new BatchParserMockEventHandler())); executionEngine.ExecuteBatch(new ScriptExecutionArgs(query, con, 15, new ExecutionEngineConditions(), new BatchParserMockEventHandler()));
Assert.Equal(ScriptExecutionResult.Success, executionResult); Assert.Equal(ScriptExecutionResult.Success, executionResult);
} }
} }
} }
// Capture the event once batch finish execution.
// Capture the event once batch finish execution.
private void OnBatchParserExecutionFinished(object sender, BatchParserExecutionFinishedEventArgs e) private void OnBatchParserExecutionFinished(object sender, BatchParserExecutionFinishedEventArgs e)
{ {
executionResult = e.ExecutionResult; executionResult = e.ExecutionResult;