-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Tables: Query support for dictionary entities and CreateFilter #12366
Changes from 2 commits
f3d0a2d
c944e90
819280c
fe112d7
98cd3b1
0945050
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,14 +37,22 @@ internal override Expression VisitBinary(BinaryExpression b) | |
{ | ||
BinaryExpression visited = (BinaryExpression)base.VisitBinary(b); | ||
|
||
if (visited.NodeType == ExpressionType.Equal) | ||
switch (visited.NodeType) | ||
{ | ||
Expression normalizedLeft = UnwrapObjectConvert(visited.Left); | ||
Expression normalizedRight = UnwrapObjectConvert(visited.Right); | ||
if (normalizedLeft != visited.Left || normalizedRight != visited.Right) | ||
{ | ||
visited = CreateRelationalOperator(ExpressionType.Equal, normalizedLeft, normalizedRight); | ||
} | ||
case ExpressionType.Equal: | ||
case ExpressionType.NotEqual: | ||
case ExpressionType.LessThan: | ||
case ExpressionType.LessThanOrEqual: | ||
case ExpressionType.GreaterThan: | ||
case ExpressionType.GreaterThanOrEqual: | ||
|
||
Expression normalizedLeft = UnwrapObjectConvert(visited.Left); | ||
Expression normalizedRight = UnwrapObjectConvert(visited.Right); | ||
if (normalizedLeft != visited.Left || normalizedRight != visited.Right) | ||
{ | ||
visited = CreateRelationalOperator(visited.NodeType, normalizedLeft, normalizedRight); | ||
} | ||
break; | ||
} | ||
|
||
if (_patterns.TryGetValue(visited.Left, out Pattern pattern) && pattern.Kind == PatternKind.Compare && IsConstantZero(visited.Right)) | ||
|
@@ -84,7 +92,7 @@ private static Expression UnwrapObjectConvert(Expression input) | |
} | ||
} | ||
|
||
while (ExpressionType.Convert == input.NodeType && typeof(object) == input.Type) | ||
while (ExpressionType.Convert == input.NodeType) | ||
{ | ||
input = ((UnaryExpression)input).Operand; | ||
} | ||
|
@@ -134,6 +142,11 @@ internal Expression VisitMethodCallNoRewrite(MethodCallExpression call) | |
return CreateCompareExpression(visited.Arguments[0], visited.Arguments[1]); | ||
} | ||
|
||
if (visited.Method == ReflectionUtil.DictionaryGetItemMethodInfo && visited.Arguments.Count == 1 && visited.Arguments[0] is ConstantExpression ce) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at ExpressionNormalizer and ExpressionWriter I wonder if we really need both or if they can be merged into one with additional benefit of avoiding creating of more Expression nodes just to throw them away. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In some cases the normalizer is simplifying the expression to be written by the writer. I'd like to do an optimization pass at some point to address issues like this and others. |
||
{ | ||
return visited; | ||
} | ||
|
||
throw new NotSupportedException($"Method {visited.Method.Name} not supported."); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,6 +50,19 @@ internal override Expression Visit(Expression exp) | |
return result; | ||
} | ||
|
||
internal override Expression VisitMethodCall(MethodCallExpression m) | ||
{ | ||
if (m.Method == ReflectionUtil.DictionaryGetItemMethodInfo && m.Arguments.Count == 1 && m.Arguments[0] is ConstantExpression ce) | ||
{ | ||
_builder.Append(ce.Value as string); | ||
} | ||
else | ||
{ | ||
return base.VisitMethodCall(m); | ||
} | ||
|
||
return m; | ||
} | ||
|
||
internal override Expression VisitMemberAccess(MemberExpression m) | ||
{ | ||
|
@@ -155,9 +168,17 @@ private void VisitOperand(Expression e) | |
{ | ||
if (e is BinaryExpression || e is UnaryExpression) | ||
{ | ||
_builder.Append(UriHelper.LEFTPAREN); | ||
Visit(e); | ||
_builder.Append(UriHelper.RIGHTPAREN); | ||
if (e is UnaryExpression unary && unary.NodeType == ExpressionType.TypeAs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a functional benefit in dropping the parenthesis for casts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case the parens are not useful. The cast is only needed for the comparison to satisfy the compiler in that all dictionary entries are |
||
{ | ||
Visit(unary.Operand); | ||
} | ||
else | ||
{ | ||
_builder.Append(UriHelper.LEFTPAREN); | ||
Visit(e); | ||
_builder.Append(UriHelper.RIGHTPAREN); | ||
} | ||
|
||
} | ||
else | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Collections.Generic; | ||
using System.Reflection; | ||
|
||
namespace Azure.Data.Tables.Queryable | ||
{ | ||
internal static class ReflectionUtil | ||
{ | ||
internal static MethodInfo DictionaryGetItemMethodInfo { get; } | ||
|
||
static ReflectionUtil() | ||
{ | ||
DictionaryGetItemMethodInfo = typeof(IDictionary<string, object>).GetMethod("get_Item"); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create Filter sounds a little bit like it's a service operation.
Who would consume this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The feature crew had a discussion last week about query functionality and the idea was raised to provide a mechanism for developers to easily see the filter query parsed out of their filter expression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, is it to support mixed query generation scenarios where you generate part of the query and manually format the other part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could be one, but there were a couple other primary concerns. Firstly, for transparency it would be nice to make it easy for developers to inspect the filter that they generate from the expression. Secondly, this makes it possible to generate and cache a query for later use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think our typical HTTP level logging is not enough in this case?
Is performance around query generation a know issue that customers hit?
I'm not strictly against exposing this feature just wonder if doing it proactively is worth it and if the client type is the right place for it (as opposed to a less visible static method somewhere).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair - Let me try it with an extension method on TableClient