/// <summary> /// Pushes an arbitrary payload to every device matching target. This is shorthand for: /// /// <code> /// var push = new AVPush(); /// push.Query = query /// push.Data = data; /// return push.SendAsync(); /// </code> /// </summary> /// <param name="data">A push payload. See the AVPush.Data property for more information.</param> /// <param name="query">A query filtering the devices which should receive this Push Notification.</param> public static Task SendDataAsync(IDictionary <string, object> data, AVQuery <AVInstallation> query) { var push = new AVPush(); push.Query = query; push.Data = data; return(push.SendAsync()); }
/// <summary> /// Pushes a simple message to every device matching the target query. This is shorthand for: /// /// <code> /// var push = new AVPush(); /// push.Query = query; /// push.Data = new Dictionary<string, object>{{"alert", alert}}; /// return push.SendAsync(); /// </code> /// </summary> /// <param name="alert">The alert message to send.</param> /// <param name="query">A query filtering the devices which should receive this Push Notification.</param> public static Task SendAlertAsync(string alert, AVQuery <AVInstallation> query) { var push = new AVPush(); push.Query = query; push.Alert = alert; return(push.SendAsync()); }
/// <summary> /// Filters a query based upon the predicate provided. /// </summary> /// <typeparam name="TSource">The type of AVObject being queried for.</typeparam> /// <param name="source">The base <see cref="AVQuery{TSource}"/> to which /// the predicate will be added.</param> /// <param name="predicate">A function to test each AVObject for a condition. /// The predicate must be able to be represented by one of the standard Where /// functions on AVQuery</param> /// <returns>A new AVQuery whose results will match the given predicate as /// well as the Source's filters.</returns> public static AVQuery <TSource> Where <TSource>( this AVQuery <TSource> source, Expression <Func <TSource, bool> > predicate) where TSource : AVObject { // Handle top-level logic operators && and || var binaryExpression = predicate.Body as BinaryExpression; if (binaryExpression != null) { if (binaryExpression.NodeType == ExpressionType.AndAlso) { return(source .Where(Expression.Lambda <Func <TSource, bool> >( binaryExpression.Left, predicate.Parameters)) .Where(Expression.Lambda <Func <TSource, bool> >( binaryExpression.Right, predicate.Parameters))); } if (binaryExpression.NodeType == ExpressionType.OrElse) { var left = source.Where(Expression.Lambda <Func <TSource, bool> >( binaryExpression.Left, predicate.Parameters)); var right = source.Where(Expression.Lambda <Func <TSource, bool> >( binaryExpression.Right, predicate.Parameters)); return(left.Or(right)); } } var normalized = new WhereNormalizer().Visit(predicate.Body); var methodCallExpr = normalized as MethodCallExpression; if (methodCallExpr != null) { return(source.WhereMethodCall(predicate, methodCallExpr)); } var binaryExpr = normalized as BinaryExpression; if (binaryExpr != null) { return(source.WhereBinaryExpression(predicate, binaryExpr)); } var unaryExpr = normalized as UnaryExpression; if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not) { var node = unaryExpr.Operand as MethodCallExpression; if (IsParseObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) { // This is a raw boolean field access like 'where !obj.Get<bool>("foo")' return(source.WhereNotEqualTo(GetValue(node.Arguments[0]) as string, true)); } } throw new InvalidOperationException( "Encountered an unsupported expression for ParseQueries."); }
/// <summary> /// Adds a constraint to the query that requires that a particular key's value /// matches another AVQuery. This only works on keys whose values are /// AVObjects or lists of AVObjects. /// </summary> /// <param name="key">The key to check.</param> /// <param name="query">The query that the value should match.</param> /// <returns>A new query with the additional constraint.</returns> public AVQuery <T> WhereMatchesQuery <TOther>(string key, AVQuery <TOther> query) where TOther : AVObject { return(new AVQuery <T>(this, where : new Dictionary <string, object> { { key, new Dictionary <string, object> { { "$inQuery", query.BuildParameters(true) } } } })); }
/// <summary> /// Adds a constraint to the query that requires that a particular key's value /// matches another AVQuery. This only works on keys whose values are /// AVObjects or lists of AVObjects. /// </summary> /// <param name="key">The key to check.</param> /// <param name="query">The query that the value should match.</param> /// <returns>A new query with the additional constraint.</returns> public virtual S WhereMatchesQuery <TOther>(string key, AVQuery <TOther> query) where TOther : AVObject { return(CreateInstance(this, where : new Dictionary <string, object> { { key, new Dictionary <string, object> { { "$inQuery", query.BuildParameters(true) } } } })); }
/// <summary> /// Private constructor for composition of queries. A Source query is required, /// but the remaining values can be null if they won't be changed in this /// composition. /// </summary> private AVQuery(AVQuery <T> source, IDictionary <string, object> where = null, IEnumerable <string> replacementOrderBy = null, IEnumerable <string> thenBy = null, int?skip = null, int?limit = null, IEnumerable <string> includes = null, IEnumerable <string> selectedKeys = null, String redirectClassNameForKey = null) : base(source, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey) { }
internal override AVQuery <T> CreateInstance( AVQuery <T> source, IDictionary <string, object> where = null, IEnumerable <string> replacementOrderBy = null, IEnumerable <string> thenBy = null, int?skip = null, int?limit = null, IEnumerable <string> includes = null, IEnumerable <string> selectedKeys = null, String redirectClassNameForKey = null) { return(new AVQuery <T>(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey)); }
/// <summary> /// Adds a constraint to the query that requires a particular key's value /// does not match any value for a key in the results of another AVQuery. /// </summary> /// <param name="key">The key whose value is being checked.</param> /// <param name="keyInQuery">The key in the objects from the subquery to look in.</param> /// <param name="query">The subquery to run</param> /// <returns>A new query with the additional constraint.</returns> public AVQuery <T> WhereDoesNotMatchesKeyInQuery <TOther>(string key, string keyInQuery, AVQuery <TOther> query) where TOther : AVObject { var parameters = new Dictionary <string, object> { { "query", query.BuildParameters(true) }, { "key", keyInQuery } }; return(new AVQuery <T>(this, where : new Dictionary <string, object> { { key, new Dictionary <string, object> { { "$dontSelect", parameters } } } })); }
/// <summary> /// Adds a constraint to the query that requires a particular key's value /// to match a value for a key in the results of another AVQuery. /// </summary> /// <param name="key">The key whose value is being checked.</param> /// <param name="keyInQuery">The key in the objects from the subquery to look in.</param> /// <param name="query">The subquery to run</param> /// <returns>A new query with the additional constraint.</returns> public virtual S WhereMatchesKeyInQuery <TOther>(string key, string keyInQuery, AVQuery <TOther> query) where TOther : AVObject { var parameters = new Dictionary <string, object> { { "query", query.BuildParameters(true) }, { "key", keyInQuery } }; return(CreateInstance(this, where : new Dictionary <string, object> { { key, new Dictionary <string, object> { { "$select", parameters } } } })); }
/// <summary> /// Converts a normalized binary expression into the appropriate AVQuery clause. /// </summary> private static AVQuery <T> WhereBinaryExpression <T>( this AVQuery <T> source, Expression <Func <T, bool> > expression, BinaryExpression node) where T : AVObject { var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression; if (!(IsParseObjectGet(leftTransformed) && leftTransformed.Object == expression.Parameters[0])) { throw new InvalidOperationException( "Where expressions must have one side be a field operation on a AVObject."); } var fieldPath = GetValue(leftTransformed.Arguments[0]) as string; var filterValue = GetValue(node.Right); if (filterValue != null && !AVEncoder.IsValidType(filterValue)) { throw new InvalidOperationException( "Where clauses must use types compatible with AVObjects."); } switch (node.NodeType) { case ExpressionType.GreaterThan: return(source.WhereGreaterThan(fieldPath, filterValue)); case ExpressionType.GreaterThanOrEqual: return(source.WhereGreaterThanOrEqualTo(fieldPath, filterValue)); case ExpressionType.LessThan: return(source.WhereLessThan(fieldPath, filterValue)); case ExpressionType.LessThanOrEqual: return(source.WhereLessThanOrEqualTo(fieldPath, filterValue)); case ExpressionType.Equal: return(source.WhereEqualTo(fieldPath, filterValue)); case ExpressionType.NotEqual: return(source.WhereNotEqualTo(fieldPath, filterValue)); default: throw new InvalidOperationException( "Where expressions do not support this operator."); } }
/// <summary> /// Constructs a AVObject whose id is already known by fetching data /// from the server. /// </summary> /// <param name="objectId">ObjectId of the AVObject to fetch.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The AVObject for the given objectId.</returns> public Task <T> GetAsync(string objectId, CancellationToken cancellationToken) { AVQuery <T> singleItemQuery = new AVQuery <T>(className). WhereEqualTo("objectId", objectId); singleItemQuery = new AVQuery <T>(singleItemQuery, includes: this.includes, selectedKeys: this.selectedKeys, limit: 1); return(singleItemQuery.FindAsync(cancellationToken).OnSuccess(t => { var result = t.Result.FirstOrDefault(); if (result == null) { throw new AVException(AVException.ErrorCode.ObjectNotFound, "Object with the given objectId not found."); } return result; })); }
/// <summary> /// Private constructor for composition of queries. A source query is required, /// but the remaining values can be null if they won't be changed in this /// composition. /// </summary> private AVQuery(AVQuery <T> source, IDictionary <string, object> where = null, IEnumerable <string> replacementOrderBy = null, IEnumerable <string> thenBy = null, int?skip = null, int?limit = null, IEnumerable <string> includes = null, IEnumerable <string> selectedKeys = null) { if (source == null) { throw new ArgumentNullException("source"); } className = source.className; this.where = source.where; this.orderBy = source.orderBy; this.skip = source.skip; this.limit = source.limit; this.includes = source.includes; this.selectedKeys = source.selectedKeys; if (where != null) { var newWhere = MergeWhereClauses(where); this.where = new Dictionary <string, object>(newWhere); } if (replacementOrderBy != null) { this.orderBy = new ReadOnlyCollection <string>(replacementOrderBy.ToList()); } if (thenBy != null) { if (this.orderBy == null) { throw new ArgumentException("You must call OrderBy before calling ThenBy."); } var newOrderBy = new List <string>(this.orderBy); newOrderBy.AddRange(thenBy); this.orderBy = new ReadOnlyCollection <string>(newOrderBy); } // Remove duplicates. if (this.orderBy != null) { var newOrderBy = new HashSet <string>(this.orderBy); this.orderBy = new ReadOnlyCollection <string>(newOrderBy.ToList <string>()); } if (skip != null) { this.skip = (this.skip ?? 0) + skip; } if (limit != null) { this.limit = limit; } if (includes != null) { var newIncludes = MergeIncludes(includes); this.includes = new ReadOnlyCollection <string>(newIncludes.ToList()); } if (selectedKeys != null) { var newSelectedKeys = MergeSelectedKeys(selectedKeys); this.selectedKeys = new ReadOnlyCollection <string>(newSelectedKeys.ToList()); } }
/// <summary> /// Constructs a query that is the or of the given queries. /// </summary> /// <typeparam name="T">The type of AVObject being queried.</typeparam> /// <param name="source">An initial query to 'or' with additional queries.</param> /// <param name="queries">The list of AVQueries to 'or' together.</param> /// <returns>A query that is the or of the given queries.</returns> public static AVQuery <T> Or <T>(this AVQuery <T> source, params AVQuery <T>[] queries) where T : AVObject { return(AVQuery <T> .Or(queries.Concat(new[] { source }))); }
/// <summary> /// Correlates the elements of two queries based on matching keys. /// </summary> /// <typeparam name="TOuter">The type of AVObjects of the first query.</typeparam> /// <typeparam name="TInner">The type of AVObjects of the second query.</typeparam> /// <typeparam name="TKey">The type of the keys returned by the key selector /// functions.</typeparam> /// <typeparam name="TResult">The type of the result. This must match either /// TOuter or TInner</typeparam> /// <param name="outer">The first query to join.</param> /// <param name="inner">The query to join to the first query.</param> /// <param name="outerKeySelector">A function to extract a join key from the results of /// the first query.</param> /// <param name="innerKeySelector">A function to extract a join key from the results of /// the second query.</param> /// <param name="resultSelector">A function to select either the outer or inner query /// result to determine which query is the base query.</param> /// <returns>A new AVQuery with a WhereMatchesQuery or WhereMatchesKeyInQuery /// clause based upon the query indicated in the <paramref name="resultSelector"/>.</returns> public static AVQuery <TResult> Join <TOuter, TInner, TKey, TResult>( this AVQuery <TOuter> outer, AVQuery <TInner> inner, Expression <Func <TOuter, TKey> > outerKeySelector, Expression <Func <TInner, TKey> > innerKeySelector, Expression <Func <TOuter, TInner, TResult> > resultSelector) where TOuter : AVObject where TInner : AVObject where TResult : AVObject { // resultSelector must select either the inner object or the outer object. If it's the inner // object, reverse the query. if (resultSelector.Body == resultSelector.Parameters[1]) { // The inner object was selected. return(inner.Join <TInner, TOuter, TKey, TInner>( outer, innerKeySelector, outerKeySelector, (i, o) => i) as AVQuery <TResult>); } if (resultSelector.Body != resultSelector.Parameters[0]) { throw new InvalidOperationException("Joins must select either the outer or inner object."); } // Normalize both selectors Expression outerNormalized = new ObjectNormalizer().Visit(outerKeySelector.Body); Expression innerNormalized = new ObjectNormalizer().Visit(innerKeySelector.Body); MethodCallExpression outerAsGet = outerNormalized as MethodCallExpression; MethodCallExpression innerAsGet = innerNormalized as MethodCallExpression; if (IsParseObjectGet(outerAsGet) && outerAsGet.Object == outerKeySelector.Parameters[0]) { var outerKey = GetValue(outerAsGet.Arguments[0]) as string; if (IsParseObjectGet(innerAsGet) && innerAsGet.Object == innerKeySelector.Parameters[0]) { // Both are key accesses, so treat this as a WhereMatchesKeyInQuery var innerKey = GetValue(innerAsGet.Arguments[0]) as string; return(outer.WhereMatchesKeyInQuery(outerKey, innerKey, inner) as AVQuery <TResult>); } if (innerKeySelector.Body == innerKeySelector.Parameters[0]) { // The inner selector is on the result of the query itself, so treat this as a // WhereMatchesQuery return(outer.WhereMatchesQuery(outerKey, inner) as AVQuery <TResult>); } throw new InvalidOperationException( "The key for the joined object must be a AVObject or a field access " + "on the AVObject."); } // TODO (hallucinogen): If we ever support "and" queries fully and/or support a "where this object // matches some key in some other query" (as opposed to requiring a key on this query), we // can add support for even more types of joins. throw new InvalidOperationException( "The key for the selected object must be a field access on the AVObject."); }
/// <summary> /// Performs a subsequent ordering of a query based upon the key selector provided. /// </summary> /// <typeparam name="TSource">The type of AVObject being queried for.</typeparam> /// <typeparam name="TSelector">The type of key returned by keySelector.</typeparam> /// <param name="source">The query to order.</param> /// <param name="keySelector">A function to extract a key from the AVObject.</param> /// <returns>A new AVQuery based on Source whose results will be ordered by /// the key specified in the keySelector.</returns> public static AVQuery <TSource> ThenByDescending <TSource, TSelector>( this AVQuery <TSource> source, Expression <Func <TSource, TSelector> > keySelector) where TSource : AVObject { return(source.ThenByDescending(GetOrderByPath(keySelector))); }
/// <summary> /// Converts a normalized method call expression into the appropriate AVQuery clause. /// </summary> private static AVQuery <T> WhereMethodCall <T>( this AVQuery <T> source, Expression <Func <T, bool> > expression, MethodCallExpression node) where T : AVObject { if (IsParseObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?))) { // This is a raw boolean field access like 'where obj.Get<bool>("foo")' return(source.WhereEqualTo(GetValue(node.Arguments[0]) as string, true)); } MethodInfo translatedMethod; if (functionMappings.TryGetValue(node.Method, out translatedMethod)) { var objTransformed = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression; if (!(IsParseObjectGet(objTransformed) && objTransformed.Object == expression.Parameters[0])) { throw new InvalidOperationException( "The left-hand side of a supported function call must be a AVObject field access."); } var fieldPath = GetValue(objTransformed.Arguments[0]); var containedIn = GetValue(node.Arguments[0]); var queryType = translatedMethod.DeclaringType.GetGenericTypeDefinition() .MakeGenericType(typeof(T)); translatedMethod = ReflectionHelpers.GetMethod(queryType, translatedMethod.Name, translatedMethod.GetParameters().Select(p => p.ParameterType).ToArray()); return(translatedMethod.Invoke(source, new[] { fieldPath, containedIn }) as AVQuery <T>); } if (node.Arguments[0] == expression.Parameters[0]) { // obj.ContainsKey("foo") --> query.WhereExists("foo") if (node.Method == containsKeyMethod) { return(source.WhereExists(GetValue(node.Arguments[1]) as string)); } // !obj.ContainsKey("foo") --> query.WhereDoesNotExist("foo") if (node.Method == notContainsKeyMethod) { return(source.WhereDoesNotExist(GetValue(node.Arguments[1]) as string)); } } if (node.Method.IsGenericMethod) { if (node.Method.GetGenericMethodDefinition() == containsMethod) { // obj.Get<IList<T>>("path").Contains(someValue) if (IsParseObjectGet(node.Arguments[0] as MethodCallExpression)) { return(source.WhereEqualTo( GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string, GetValue(node.Arguments[1]))); } // someList.Contains(obj.Get<T>("path")) if (IsParseObjectGet(node.Arguments[1] as MethodCallExpression)) { var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; return(source.WhereContainedIn( GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string, collection.Cast <object>())); } } if (node.Method.GetGenericMethodDefinition() == notContainsMethod) { // !obj.Get<IList<T>>("path").Contains(someValue) if (IsParseObjectGet(node.Arguments[0] as MethodCallExpression)) { return(source.WhereNotEqualTo( GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string, GetValue(node.Arguments[1]))); } // !someList.Contains(obj.Get<T>("path")) if (IsParseObjectGet(node.Arguments[1] as MethodCallExpression)) { var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable; return(source.WhereNotContainedIn( GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string, collection.Cast <object>())); } } } throw new InvalidOperationException(node.Method + " is not a supported method call in a where expression."); }