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

Query : Adds string comparison alternative when converting LINQ to SQL #3668

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c983055
string.Compare supported with LINQ to SQL
ernesto1596 Jan 27, 2023
9b944ac
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Feb 8, 2023
2d3d232
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jun 13, 2023
8412d2a
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jun 14, 2023
e25b814
Update tests
ernesto1596 Jun 14, 2023
3ae9b0e
Update test name
ernesto1596 Jun 14, 2023
8236ec2
Update tests
ernesto1596 Jun 14, 2023
1fd3fba
Add test
ernesto1596 Jun 14, 2023
8db7c5c
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jun 14, 2023
2ec00e5
Create helper ReverseExpressionTypeForStrings
ernesto1596 Jun 14, 2023
fd56903
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jun 15, 2023
890ac4e
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jun 21, 2023
26988c7
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jun 23, 2023
eca95b6
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jul 6, 2023
c0d5805
Merge branch 'master' into ernestoc/stringCompareTo
adityasa Jul 7, 2023
60f933d
Merge branch 'master' into ernestoc/stringCompareTo
adityasa Jul 8, 2023
36a8f3d
PR feedback
ernesto1596 Jul 10, 2023
e155b8f
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jul 10, 2023
ddd2498
Merge branch 'master' into ernestoc/stringCompareTo
ernesto1596 Jul 10, 2023
4b25820
Update tests
ernesto1596 Jul 10, 2023
b3e3620
Update base line
ernesto1596 Jul 10, 2023
6ef4f90
Merge branch 'master' into ernestoc/stringCompareTo
ealsur Jul 11, 2023
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
74 changes: 74 additions & 0 deletions Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,10 @@ private static SqlScalarExpression VisitBinary(BinaryExpression inputExpression,
{
return ExpressionToSql.VisitStringCompareTo(methodCallExpression, constantExpression, inputExpression.NodeType, reverseNodeType, context);
}
if (TryMatchStringCompare(methodCallExpression, constantExpression, inputExpression.NodeType))
{
ernesto1596 marked this conversation as resolved.
Show resolved Hide resolved
return ExpressionToSql.VisitStringCompare(methodCallExpression, constantExpression, inputExpression.NodeType, reverseNodeType, context);
}
}

SqlScalarExpression left = ExpressionToSql.VisitScalarExpression(inputExpression.Left, context);
Expand Down Expand Up @@ -645,6 +649,76 @@ private static SqlScalarExpression VisitStringCompareTo(
return SqlBinaryScalarExpression.Create(op, leftExpression, rightExpression);
}

private static bool TryMatchStringCompare(MethodCallExpression left, ConstantExpression right, ExpressionType compareOperator)
{
if (left.Method.Equals(typeof(string).GetMethod("Compare", new Type[] { typeof(string), typeof(string) })) && left.Arguments.Count == 2)
{
// operator can only be =, >, >=, <, <=
switch (compareOperator)
{
case ExpressionType.Equal:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
break;
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.StringCompareToInvalidOperator));
}

// the constant value should be zero, otherwise we can't determine how to translate the expression
// it could be either integer or nullable integer
if (!(right.Type == typeof(int) && (int)right.Value == 0) &&
!(right.Type == typeof(int?) && ((int?)right.Value).HasValue && ((int?)right.Value).Value == 0))
{
ernesto1596 marked this conversation as resolved.
Show resolved Hide resolved
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.StringCompareToInvalidConstant));
}

return true;
}

return false;
}

private static SqlScalarExpression VisitStringCompare(
MethodCallExpression left,
ConstantExpression right,
ExpressionType compareOperator,
bool reverseNodeType,
TranslationContext context)
{
if (reverseNodeType)
{
switch (compareOperator)
{
case ExpressionType.Equal:
// do nothing
break;
case ExpressionType.GreaterThan:
compareOperator = ExpressionType.LessThan;
break;
case ExpressionType.GreaterThanOrEqual:
compareOperator = ExpressionType.LessThanOrEqual;
break;
case ExpressionType.LessThan:
compareOperator = ExpressionType.GreaterThan;
break;
case ExpressionType.LessThanOrEqual:
compareOperator = ExpressionType.GreaterThanOrEqual;
break;
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.StringCompareToInvalidOperator));
}
}

SqlBinaryScalarOperatorKind op = GetBinaryOperatorKind(compareOperator, null);

SqlScalarExpression leftExpression = ExpressionToSql.VisitNonSubqueryScalarExpression(left.Arguments[0], context);
SqlScalarExpression rightExpression = ExpressionToSql.VisitNonSubqueryScalarExpression(left.Arguments[1], context);

return SqlBinaryScalarExpression.Create(op, leftExpression, rightExpression);
}

private static SqlScalarExpression VisitTypeIs(TypeBinaryExpression inputExpression, TranslationContext context)
{
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.ExpressionTypeIsNotSupported, inputExpression.NodeType));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<Results>
<Result>
<Input>
<Description><![CDATA[Projected Compare ==]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, doc.StringField2) == 0))]]></Expression>
ernesto1596 marked this conversation as resolved.
Show resolved Hide resolved
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] = root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare >]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, doc.StringField2) > 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] > root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare >=]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, doc.StringField2) >= 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] >= root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare <]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, doc.StringField2) < 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] < root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare <=]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, doc.StringField2) <= 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] <= root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare static string ==]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") == 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] = "str")
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare static string >]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") > 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] > "str")
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare static string >=]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") >= 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] >= "str")
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare static string <]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") < 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] < "str")
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare static string <=]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") <= 0))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] <= "str")
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare == reverse operands]]></Description>
<Expression><![CDATA[query.Select(doc => (0 == Compare(doc.StringField, doc.StringField2)))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] = root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare < reverse operands]]></Description>
<Expression><![CDATA[query.Select(doc => (0 < Compare(doc.StringField, doc.StringField2)))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] > root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare <= reverse operands]]></Description>
<Expression><![CDATA[query.Select(doc => (0 <= Compare(doc.StringField, doc.StringField2)))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] >= root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare > reverse operands]]></Description>
<Expression><![CDATA[query.Select(doc => (0 > Compare(doc.StringField, doc.StringField2)))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] < root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Projected Compare >= reverse operands]]></Description>
<Expression><![CDATA[query.Select(doc => (0 >= Compare(doc.StringField, doc.StringField2)))]]></Expression>
</Input>
<Output>
<SqlQuery>
<![CDATA[
SELECT VALUE (root["StringField"] <= root["StringField2"])
FROM root]]>
</SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare > 1]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") > 1))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[The right hand side of string.CompareTo() comparison must be constant '0']]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare == 1]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") == 1))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[The right hand side of string.CompareTo() comparison must be constant '0']]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare == -1]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") == -1))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[The right hand side of string.CompareTo() comparison must be constant '0']]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare | 0]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") | 0))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Invalid operator for string.CompareTo(). Vaid operators are ('==', '<', '<=', '>' or '>=')]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare & 0]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") & 0))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Invalid operator for string.CompareTo(). Vaid operators are ('==', '<', '<=', '>' or '>=')]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Compare ^ 0]]></Description>
<Expression><![CDATA[query.Select(doc => (Compare(doc.StringField, "str") ^ 0))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Invalid operator for string.CompareTo(). Vaid operators are ('==', '<', '<=', '>' or '>=')]]></ErrorMessage>
</Output>
</Result>
</Results>
Loading