diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs index 7ca3a60a71..fd92b233a8 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Query.Core.Parser using System.Collections.Immutable; using System.Diagnostics.Contracts; using System.Globalization; + using System.Text; using Antlr4.Runtime.Misc; using Antlr4.Runtime.Tree; using Microsoft.Azure.Cosmos.SqlObjects; @@ -950,8 +951,45 @@ public UnknownSqlObjectException(SqlObject sqlObject, Exception innerException = private static string GetStringValueFromNode(IParseTree parseTree) { string text = parseTree.GetText(); - string textWithoutQuotes = text.Substring(1, text.Length - 2).Replace("\\\"", "\""); - return textWithoutQuotes; + + // 1. Remove leading and trailing (single or double) quotes. + // 2. Unescape following characters: + // \" => " + // \\ => \ + // \/ => / + // Based on the documentation, we should also escape single quote \'. + // https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/constants#remarks + // However that's failing in the parser (before reaching this point) and will be fixed separately (after checking server's behavior). + StringBuilder stringBuilder = new StringBuilder(text.Length); + for (int index = 1; index < text.Length - 1; index++) + { + switch (text[index]) + { + case '\\': + if ((index + 1) < (text.Length - 1)) + { + switch (text[index + 1]) + { + case '"': + case '\\': + case '/': + stringBuilder.Append(text[index + 1]); + index++; + break; + default: + stringBuilder.Append(text[index]); + break; + } + } + break; + + default: + stringBuilder.Append(text[index]); + break; + } + } + + return stringBuilder.ToString(); } private static Number64 GetNumber64ValueFromNode(IParseTree parseTree) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/BaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/BaselineTests.cs index 6e2162ee2f..419a29229b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/BaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/BaselineTests.cs @@ -135,8 +135,8 @@ public void ExecuteTestSuite(IEnumerable inputs, [CallerMemberName] stri Assert.IsTrue( matched, - $@" Baseline File {baselinePath}, - Output File {outputPath}, + $@" Baseline File {Path.GetFullPath(baselinePath)}, + Output File {Path.GetFullPath(outputPath)}, Expected: {baselineTextSuffix}, Actual: {outputTextSuffix}"); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/ScalarExpressionSqlParserBaselineTests.StringLiteral.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/ScalarExpressionSqlParserBaselineTests.StringLiteral.xml new file mode 100644 index 0000000000..df9e44ceda --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/ScalarExpressionSqlParserBaselineTests.StringLiteral.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj index 5b429468d8..88450d8984 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj @@ -82,6 +82,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs index 43377f0074..10f6fe3ee0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs @@ -520,6 +520,30 @@ public void OrderOfOperation() this.ExecuteTestSuite(inputs); } + [TestMethod] + public void StringLiteral() + { + List inputs = new List() + { + // Single quoted string literals do not allow ' even when it's escaped. + // Parser currently fails with Antlr4.Runtime.NoViableAltException + CreateInput( + description: @"Single quoted string literals with escape seqence", + scalarExpression: @"['\""DoubleQuote', '\\ReverseSolidus', '\/solidus', '\bBackspace', '\fSeparatorFeed', '\nLineFeed', '\rCarriageReturn', '\tTab', '\u1234']"), + CreateInput( + description: @"Double quoted string literals with escape seqence", + scalarExpression: @"[""'SingleQuote"", ""\""DoubleQuote"", ""\\ReverseSolidus"", ""\/solidus"", ""\bBackspace"", ""\fSeparatorFeed"", ""\nLineFeed"", ""\rCarriageReturn"", ""\tTab"", ""\u1234""]"), + CreateInput( + description: @"Single quoted string literals special cases", + scalarExpression: @"['\""', '\""\""', '\\', '\\\\', '\/', '\/\/', '\b', '\b\b', '\f', '\f\f', '\n', '\n\n', '\r', '\r\r', '\t', '\t\t', '\u1234', '\u1234\u1234']"), + CreateInput( + description: @"Double quoted string literals special cases", + scalarExpression: @"[""\"""", ""\""\"""", ""\\"", ""\\\\"", ""\/"", ""\/\/"", ""\b"", ""\b\b"", ""\f"", ""\f\f"", ""\n"", ""\n\n"", ""\r"", ""\r\r"", ""\t"", ""\t\t"", ""\u1234"", ""\u1234\u1234""]"), + }; + + this.ExecuteTestSuite(inputs); + } + public static SqlParserBaselineTestInput CreateInput(string description, string scalarExpression) { return new SqlParserBaselineTestInput(description, $"SELECT VALUE {scalarExpression}");