Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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).
adityasa marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ public void ExecuteTestSuite(IEnumerable<TInput> 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}");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,30 @@ public void OrderOfOperation()
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
adityasa marked this conversation as resolved.
Show resolved Hide resolved
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""]"),
};
adityasa marked this conversation as resolved.
Show resolved Hide resolved

this.ExecuteTestSuite(inputs);
}

public static SqlParserBaselineTestInput CreateInput(string description, string scalarExpression)
{
return new SqlParserBaselineTestInput(description, $"SELECT VALUE {scalarExpression}");
Expand Down
Loading