[Internal] Query: Fixes escaped string parsing in SqlParser (#4054)

* Initial commit

* Addressed comments.
This commit is contained in:
Aditya 2023-09-18 11:56:55 -07:00 коммит произвёл GitHub
Родитель 83509cc173
Коммит 258d960ae3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 107 добавлений и 4 удалений

Просмотреть файл

@ -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 @@ namespace Microsoft.Azure.Cosmos.Query.Core.Parser
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)

Просмотреть файл

@ -135,8 +135,8 @@ namespace Microsoft.Azure.Cosmos.Test.BaselineTest
Assert.IsTrue(
matched,
$@" Baseline File {baselinePath},
Output File {outputPath},
$@" Baseline File {Path.GetFullPath(baselinePath)},
Output File {Path.GetFullPath(outputPath)},
Expected: {baselineTextSuffix},
Actual: {outputTextSuffix}");
}

Просмотреть файл

@ -0,0 +1,38 @@
<Results>
<Result>
<Input>
<Description><![CDATA[Single quoted string literals with escape seqence]]></Description>
<Query><![CDATA[SELECT VALUE ['\"DoubleQuote', '\\ReverseSolidus', '\/solidus', '\bBackspace', '\fSeparatorFeed', '\nLineFeed', '\rCarriageReturn', '\tTab', '\u1234']]]></Query>
</Input>
<Output>
<ParsedQuery><![CDATA[SELECT VALUE ["\"DoubleQuote", "\\ReverseSolidus", "/solidus", "\\bBackspace", "\\fSeparatorFeed", "\\nLineFeed", "\\rCarriageReturn", "\\tTab", "\\u1234"]]]></ParsedQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Double quoted string literals with escape seqence]]></Description>
<Query><![CDATA[SELECT VALUE ["'SingleQuote", "\"DoubleQuote", "\\ReverseSolidus", "\/solidus", "\bBackspace", "\fSeparatorFeed", "\nLineFeed", "\rCarriageReturn", "\tTab", "\u1234"]]]></Query>
</Input>
<Output>
<ParsedQuery><![CDATA[SELECT VALUE ["'SingleQuote", "\"DoubleQuote", "\\ReverseSolidus", "/solidus", "\\bBackspace", "\\fSeparatorFeed", "\\nLineFeed", "\\rCarriageReturn", "\\tTab", "\\u1234"]]]></ParsedQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Single quoted string literals special cases]]></Description>
<Query><![CDATA[SELECT VALUE ['\"', '\"\"', '\\', '\\\\', '\/', '\/\/', '\b', '\b\b', '\f', '\f\f', '\n', '\n\n', '\r', '\r\r', '\t', '\t\t', '\u1234', '\u1234\u1234']]]></Query>
</Input>
<Output>
<ParsedQuery><![CDATA[SELECT VALUE ["\"", "\"\"", "\\", "\\\\", "/", "//", "\\b", "\\b\\b", "\\f", "\\f\\f", "\\n", "\\n\\n", "\\r", "\\r\\r", "\\t", "\\t\\t", "\\u1234", "\\u1234\\u1234"]]]></ParsedQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Double quoted string literals special cases]]></Description>
<Query><![CDATA[SELECT VALUE ["\"", "\"\"", "\\", "\\\\", "\/", "\/\/", "\b", "\b\b", "\f", "\f\f", "\n", "\n\n", "\r", "\r\r", "\t", "\t\t", "\u1234", "\u1234\u1234"]]]></Query>
</Input>
<Output>
<ParsedQuery><![CDATA[SELECT VALUE ["\"", "\"\"", "\\", "\\\\", "/", "//", "\\b", "\\b\\b", "\\f", "\\f\\f", "\\n", "\\n\\n", "\\r", "\\r\\r", "\\t", "\\t\\t", "\\u1234", "\\u1234\\u1234"]]]></ParsedQuery>
</Output>
</Result>
</Results>

Просмотреть файл

@ -82,6 +82,9 @@
<None Update="BaselineTest\TestBaseline\GroupByClauseSqlParserBaselineTests.Tests.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="BaselineTest\TestBaseline\ScalarExpressionSqlParserBaselineTests.StringLiteral.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="BaselineTest\TestBaseline\SelectClauseSqlParserBaselineTests.Tests.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

Просмотреть файл

@ -520,6 +520,30 @@
this.ExecuteTestSuite(inputs);
}
[TestMethod]
public void StringLiteral()
{
List<SqlParserBaselineTestInput> inputs = new List<SqlParserBaselineTestInput>()
{
// 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}");