Skip to content

Commit

Permalink
Tables: Query support for dictionary entities and CreateFilter (#12366)
Browse files Browse the repository at this point in the history
* Support for dictionary entities and CreateFilter

* add comment to CreateFilter

* take the new Azure.ClientSdk.Analyzers and remove pragmas

* remove redundant .ToList() calls in tests

* make CreateFilter less prominant on the client
  • Loading branch information
christothes authored Jun 1, 2020
1 parent b6d7f43 commit 6e83f93
Show file tree
Hide file tree
Showing 15 changed files with 3,803 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ protected TableClient() { }
public virtual Azure.Response<T> Insert<T>(T entity, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where T : Azure.Data.Tables.TableEntity, new() { throw null; }
public virtual Azure.Response Merge(System.Collections.Generic.IDictionary<string, object> entity, string eTag = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response> MergeAsync(System.Collections.Generic.IDictionary<string, object> entity, string eTag = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Pageable<System.Collections.Generic.IDictionary<string, object>> Query(string select = null, string filter = null, int? top = default(int?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.AsyncPageable<System.Collections.Generic.IDictionary<string, object>> QueryAsync(string select = null, string filter = null, int? top = default(int?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.AsyncPageable<T> QueryAsync<T>(System.Linq.Expressions.Expression<System.Func<T, bool>> filter, string select = null, int? top = default(int?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where T : Azure.Data.Tables.TableEntity, new() { throw null; }
public virtual Azure.Pageable<System.Collections.Generic.IDictionary<string, object>> Query(System.Linq.Expressions.Expression<System.Func<System.Collections.Generic.IDictionary<string, object>, bool>> filter, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Pageable<System.Collections.Generic.IDictionary<string, object>> Query(string filter = null, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.AsyncPageable<System.Collections.Generic.IDictionary<string, object>> QueryAsync(System.Linq.Expressions.Expression<System.Func<System.Collections.Generic.IDictionary<string, object>, bool>> filter, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.AsyncPageable<System.Collections.Generic.IDictionary<string, object>> QueryAsync(string filter = null, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.AsyncPageable<T> QueryAsync<T>(System.Linq.Expressions.Expression<System.Func<T, bool>> filter, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where T : Azure.Data.Tables.TableEntity, new() { throw null; }
public virtual Azure.AsyncPageable<T> QueryAsync<T>(string filter = null, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where T : Azure.Data.Tables.TableEntity, new() { throw null; }
public virtual Azure.Pageable<T> Query<T>(System.Linq.Expressions.Expression<System.Func<T, bool>> filter, string select = null, int? top = default(int?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where T : Azure.Data.Tables.TableEntity, new() { throw null; }
public virtual Azure.Pageable<T> Query<T>(System.Linq.Expressions.Expression<System.Func<T, bool>> filter, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where T : Azure.Data.Tables.TableEntity, new() { throw null; }
public virtual Azure.Pageable<T> Query<T>(string filter = null, int? top = default(int?), string select = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) where T : Azure.Data.Tables.TableEntity, new() { throw null; }
public virtual Azure.Response SetAccessPolicy(System.Collections.Generic.IEnumerable<Azure.Data.Tables.Models.SignedIdentifier> tableAcl, int? timeout = default(int?), string requestId = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response> SetAccessPolicyAsync(System.Collections.Generic.IEnumerable<Azure.Data.Tables.Models.SignedIdentifier> tableAcl = null, int? timeout = default(int?), string requestId = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down Expand Up @@ -219,6 +221,13 @@ internal TableServiceStats() { }
public Azure.Data.Tables.Models.GeoReplication GeoReplication { get { throw null; } }
}
}
namespace Azure.Data.Tables.Queryable
{
public static partial class TableClientExtensions
{
public static string CreateFilter<T>(this Azure.Data.Tables.TableClient client, System.Linq.Expressions.Expression<System.Func<T, bool>> filter) { throw null; }
}
}
namespace Azure.Data.Tables.Sas
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Azure.Data.Tables.Queryable
{
public static class TableClientExtensions
{
/// <summary>
/// Creates an Odata filter query string from the provided expression.
/// </summary>
/// <typeparam name="T">The type of the entity being queried. Typically this will be derrived from <see cref="TableEntity"/> or <see cref="Dictionary{String, Object}"/>.</typeparam>
/// <param name="client">The <see cref="TableClient"/>.</param>
/// <param name="filter">A filter expresssion.</param>
/// <returns>The string representation of the filter expression.</returns>
public static string CreateFilter<T>(this TableClient client, Expression<Func<T, bool>> filter) => client.Bind(filter);
}
}
29 changes: 21 additions & 8 deletions sdk/tables/Azure.Data.Tables/src/Queryable/ExpressionNormalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
{
return visited;
}

throw new NotSupportedException($"Method {visited.Method.Name} not supported.");
}

Expand Down
27 changes: 24 additions & 3 deletions sdk/tables/Azure.Data.Tables/src/Queryable/ExpressionWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Visit(unary.Operand);
}
else
{
_builder.Append(UriHelper.LEFTPAREN);
Visit(e);
_builder.Append(UriHelper.RIGHTPAREN);
}

}
else
{
Expand Down
18 changes: 18 additions & 0 deletions sdk/tables/Azure.Data.Tables/src/Queryable/ReflectionUtil.cs
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");
}
}
}
31 changes: 22 additions & 9 deletions sdk/tables/Azure.Data.Tables/src/TableClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,13 @@ public virtual Response Merge(IDictionary<string, object> entity, string eTag =
/// <summary>
/// Queries entities in the table.
/// </summary>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="filter">Returns only tables or entities that satisfy the specified filter.</param>
/// <param name="top">Returns only the top n tables or entities from the set.</param>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns></returns>
[ForwardsClientCalls]
public virtual AsyncPageable<IDictionary<string, object>> QueryAsync(string select = null, string filter = null, int? top = null, CancellationToken cancellationToken = default)
public virtual AsyncPageable<IDictionary<string, object>> QueryAsync(string filter = null, int? top = null, string select = null, CancellationToken cancellationToken = default)
{
return PageableHelpers.CreateAsyncEnumerable(async _ =>
{
Expand Down Expand Up @@ -450,13 +450,13 @@ public virtual AsyncPageable<IDictionary<string, object>> QueryAsync(string sele
/// <summary>
/// Queries entities in the table.
/// </summary>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="filter">Returns only tables or entities that satisfy the specified filter.</param>
/// <param name="top">Returns only the top n tables or entities from the set.</param>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>

[ForwardsClientCalls]
public virtual Pageable<IDictionary<string, object>> Query(string select = null, string filter = null, int? top = null, CancellationToken cancellationToken = default)
public virtual Pageable<IDictionary<string, object>> Query(string filter = null, int? top = null, string select = null, CancellationToken cancellationToken = default)
{
return PageableHelpers.CreateEnumerable(_ =>
{
Expand Down Expand Up @@ -493,14 +493,27 @@ public virtual Pageable<IDictionary<string, object>> Query(string select = null,
/// Queries entities in the table.
/// </summary>
/// <param name="filter">Returns only tables or entities that satisfy the specified filter.</param>
/// <param name="top">Returns only the top n tables or entities from the set.</param>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>

[ForwardsClientCalls]
public virtual AsyncPageable<IDictionary<string, object>> QueryAsync(Expression<Func<IDictionary<string, object>, bool>> filter, int? top = null, string select = null, CancellationToken cancellationToken = default) =>
QueryAsync(Bind(filter), top, select, cancellationToken);

public virtual Pageable<IDictionary<string, object>> Query(Expression<Func<IDictionary<string, object>, bool>> filter, int? top = null, string select = null, CancellationToken cancellationToken = default) =>
Query(Bind(filter), top, select, cancellationToken);

/// <summary>
/// Queries entities in the table.
/// </summary>
/// <param name="filter">Returns only tables or entities that satisfy the specified filter.</param>
/// <param name="top">Returns only the top n tables or entities from the set.</param>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>

[ForwardsClientCalls]
#pragma warning disable AZC0004 // DO provide both asynchronous and synchronous variants for all service methods.
public virtual AsyncPageable<T> QueryAsync<T>(Expression<Func<T, bool>> filter, string select = null, int? top = null, CancellationToken cancellationToken = default) where T : TableEntity, new() =>
#pragma warning restore AZC0004 // DO provide both asynchronous and synchronous variants for all service methods.
public virtual AsyncPageable<T> QueryAsync<T>(Expression<Func<T, bool>> filter, int? top = null, string select = null, CancellationToken cancellationToken = default) where T : TableEntity, new() =>
QueryAsync<T>(Bind(filter), top, select, cancellationToken);

/// <summary>
Expand Down Expand Up @@ -545,12 +558,12 @@ public virtual Pageable<IDictionary<string, object>> Query(string select = null,
/// Queries entities in the table.
/// </summary>
/// <param name="filter">Returns only tables or entities that satisfy the specified filter.</param>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="top">Returns only the top n tables or entities from the set.</param>
/// <param name="select">Returns the desired properties of an entity from the set. </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>

[ForwardsClientCalls]
public virtual Pageable<T> Query<T>(Expression<Func<T, bool>> filter, string select = null, int? top = null, CancellationToken cancellationToken = default) where T : TableEntity, new() =>
public virtual Pageable<T> Query<T>(Expression<Func<T, bool>> filter, int? top = null, string select = null, CancellationToken cancellationToken = default) where T : TableEntity, new() =>
Query<T>(Bind(filter), top, select, cancellationToken);

/// <summary>
Expand Down
Loading

0 comments on commit 6e83f93

Please sign in to comment.