using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using LeanCloud.Storage.Internal;
namespace LeanCloud
{
///
/// Query 对象的基础接口
///
public interface IAVQuery
{
}
///
/// LeanCloud 存储对象的接触接口
///
public interface IAVObject
{
}
public abstract class AVQueryBase : IAVQuery
where T : IAVObject
{
internal string className;
internal Dictionary where;
internal ReadOnlyCollection orderBy;
internal ReadOnlyCollection includes;
internal ReadOnlyCollection selectedKeys;
internal String redirectClassNameForKey;
internal int? skip;
internal int? limit;
///
/// 构建查询字符串
///
/// 是否包含 ClassName
///
public IDictionary BuildParameters(bool includeClassName = false)
{
Dictionary result = new Dictionary();
if (where != null)
{
result["where"] = PointerOrLocalIdEncoder.Instance.Encode(where);
}
if (orderBy != null)
{
result["order"] = string.Join(",", orderBy.ToArray());
}
if (skip != null)
{
result["skip"] = skip.Value;
}
if (limit != null)
{
result["limit"] = limit.Value;
}
if (includes != null)
{
result["include"] = string.Join(",", includes.ToArray());
}
if (selectedKeys != null)
{
result["keys"] = string.Join(",", selectedKeys.ToArray());
}
if (includeClassName)
{
result["className"] = className;
}
if (redirectClassNameForKey != null)
{
result["redirectClassNameForKey"] = redirectClassNameForKey;
}
return result;
}
public virtual Dictionary Where
{
get
{
return this.where;
}
set
{
this.where = value;
}
}
public virtual IDictionary MergeWhere(IDictionary primary, IDictionary secondary)
{
if (secondary == null)
{
return primary;
}
var newWhere = new Dictionary(primary);
foreach (var pair in secondary)
{
var condition = pair.Value as IDictionary;
if (newWhere.ContainsKey(pair.Key))
{
var oldCondition = newWhere[pair.Key] as IDictionary;
if (oldCondition == null || condition == null)
{
throw new ArgumentException("More than one where clause for the given key provided.");
}
var newCondition = new Dictionary(oldCondition);
foreach (var conditionPair in condition)
{
if (newCondition.ContainsKey(conditionPair.Key))
{
throw new ArgumentException("More than one condition for the given key provided.");
}
newCondition[conditionPair.Key] = conditionPair.Value;
}
newWhere[pair.Key] = newCondition;
}
else
{
newWhere[pair.Key] = pair.Value;
}
}
return newWhere;
}
}
public abstract class AVQueryPair
where S : IAVQuery
where T : IAVObject
{
protected readonly string className;
protected readonly Dictionary where;
protected readonly ReadOnlyCollection orderBy;
protected readonly ReadOnlyCollection includes;
protected readonly ReadOnlyCollection selectedKeys;
protected readonly string redirectClassNameForKey;
protected readonly int? skip;
protected readonly int? limit;
internal string ClassName { get { return className; } }
private string relativeUri;
internal string RelativeUri
{
get
{
string rtn = string.Empty;
if (string.IsNullOrEmpty(relativeUri))
{
rtn = "classes/" + Uri.EscapeDataString(this.className);
}
else
{
rtn = relativeUri;
}
return rtn;
}
set
{
relativeUri = value;
}
}
public Dictionary Condition
{
get { return this.where; }
}
protected AVQueryPair()
{
}
public abstract S CreateInstance(IDictionary where = null,
IEnumerable replacementOrderBy = null,
IEnumerable thenBy = null,
int? skip = null,
int? limit = null,
IEnumerable includes = null,
IEnumerable selectedKeys = null,
string redirectClassNameForKey = null);
///
/// 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.
///
protected AVQueryPair(AVQueryPair source,
IDictionary where = null,
IEnumerable replacementOrderBy = null,
IEnumerable thenBy = null,
int? skip = null,
int? limit = null,
IEnumerable includes = null,
IEnumerable selectedKeys = null,
string redirectClassNameForKey = 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;
this.redirectClassNameForKey = source.redirectClassNameForKey;
if (where != null)
{
var newWhere = MergeWhereClauses(where);
this.where = new Dictionary(newWhere);
}
if (replacementOrderBy != null)
{
this.orderBy = new ReadOnlyCollection(replacementOrderBy.ToList());
}
if (thenBy != null)
{
if (this.orderBy == null)
{
throw new ArgumentException("You must call OrderBy before calling ThenBy.");
}
var newOrderBy = new List(this.orderBy);
newOrderBy.AddRange(thenBy);
this.orderBy = new ReadOnlyCollection(newOrderBy);
}
// Remove duplicates.
if (this.orderBy != null)
{
var newOrderBy = new HashSet(this.orderBy);
this.orderBy = new ReadOnlyCollection(newOrderBy.ToList());
}
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(newIncludes.ToList());
}
if (selectedKeys != null)
{
var newSelectedKeys = MergeSelectedKeys(selectedKeys);
this.selectedKeys = new ReadOnlyCollection(newSelectedKeys.ToList());
}
if (redirectClassNameForKey != null)
{
this.redirectClassNameForKey = redirectClassNameForKey;
}
}
public AVQueryPair(string className)
{
if (string.IsNullOrEmpty(className))
{
throw new ArgumentNullException("className", "Must specify a AVObject class name when creating a AVQuery.");
}
this.className = className;
}
private HashSet MergeIncludes(IEnumerable includes)
{
if (this.includes == null)
{
return new HashSet(includes);
}
var newIncludes = new HashSet(this.includes);
foreach (var item in includes)
{
newIncludes.Add(item);
}
return newIncludes;
}
private HashSet MergeSelectedKeys(IEnumerable selectedKeys)
{
if (this.selectedKeys == null)
{
return new HashSet(selectedKeys);
}
var newSelectedKeys = new HashSet(this.selectedKeys);
foreach (var item in selectedKeys)
{
newSelectedKeys.Add(item);
}
return newSelectedKeys;
}
private IDictionary MergeWhereClauses(IDictionary where)
{
return MergeWhere(this.where, where);
}
public virtual IDictionary MergeWhere(IDictionary primary, IDictionary secondary)
{
if (secondary == null)
{
return primary;
}
if (primary == null)
{
return secondary;
}
var newWhere = new Dictionary(primary);
foreach (var pair in secondary)
{
var condition = pair.Value as IDictionary;
if (newWhere.ContainsKey(pair.Key))
{
var oldCondition = newWhere[pair.Key] as IDictionary;
if (oldCondition == null || condition == null)
{
throw new ArgumentException("More than one where clause for the given key provided.");
}
var newCondition = new Dictionary(oldCondition);
foreach (var conditionPair in condition)
{
if (newCondition.ContainsKey(conditionPair.Key))
{
throw new ArgumentException("More than one condition for the given key provided.");
}
newCondition[conditionPair.Key] = conditionPair.Value;
}
newWhere[pair.Key] = newCondition;
}
else
{
newWhere[pair.Key] = pair.Value;
}
}
return newWhere;
}
///
/// Constructs a query that is the or of the given queries.
///
/// The list of AVQueries to 'or' together.
/// A AVeQquery that is the 'or' of the passed in queries.
public static Q Or(IEnumerable queries)
where Q : AVQueryBase
where O : IAVObject
{
string className = null;
var orValue = new List>();
// We need to cast it to non-generic IEnumerable because of AOT-limitation
var nonGenericQueries = (IEnumerable)queries;
Q current = null;
foreach (var obj in nonGenericQueries)
{
var q = (Q)obj;
current = q;
if (className != null && q.className != className)
{
throw new ArgumentException(
"All of the queries in an or query must be on the same class.");
}
className = q.className;
var parameters = q.BuildParameters();
if (parameters.Count == 0)
{
continue;
}
object where;
if (!parameters.TryGetValue("where", out where) || parameters.Count > 1)
{
throw new ArgumentException(
"None of the queries in an or query can have non-filtering clauses");
}
orValue.Add(where as IDictionary);
}
current.Where = new Dictionary()
{
{"$or", orValue}
};
return current;
}
#region Order By
///
/// Sorts the results in ascending order by the given key.
/// This will override any existing ordering for the query.
///
/// The key to order by.
/// A new query with the additional constraint.
public virtual S OrderBy(string key)
{
return CreateInstance(replacementOrderBy: new List { key });
}
///
/// Sorts the results in descending order by the given key.
/// This will override any existing ordering for the query.
///
/// The key to order by.
/// A new query with the additional constraint.
public virtual S OrderByDescending(string key)
{
return CreateInstance(replacementOrderBy: new List { "-" + key });
}
///
/// Sorts the results in ascending order by the given key, after previous
/// ordering has been applied.
///
/// This method can only be called if there is already an
/// or
/// on this query.
///
/// The key to order by.
/// A new query with the additional constraint.
public virtual S ThenBy(string key)
{
return CreateInstance(thenBy: new List { key });
}
///
/// Sorts the results in descending order by the given key, after previous
/// ordering has been applied.
///
/// This method can only be called if there is already an
/// or on this query.
///
/// The key to order by.
/// A new query with the additional constraint.
public virtual S ThenByDescending(string key)
{
return CreateInstance(thenBy: new List { "-" + key });
}
#endregion
///
/// Include nested AVObjects for the provided key. You can use dot notation
/// to specify which fields in the included objects should also be fetched.
///
/// The key that should be included.
/// A new query with the additional constraint.
public virtual S Include(string key)
{
return CreateInstance(includes: new List { key });
}
///
/// Restrict the fields of returned AVObjects to only include the provided key.
/// If this is called multiple times, then all of the keys specified in each of
/// the calls will be included.
///
/// The key that should be included.
/// A new query with the additional constraint.
public virtual S Select(string key)
{
return CreateInstance(selectedKeys: new List { key });
}
///
/// Skips a number of results before returning. This is useful for pagination
/// of large queries. Chaining multiple skips together will cause more results
/// to be skipped.
///
/// The number of results to skip.
/// A new query with the additional constraint.
public virtual S Skip(int count)
{
return CreateInstance(skip: count);
}
///
/// Controls the maximum number of results that are returned. Setting a negative
/// limit denotes retrieval without a limit. Chaining multiple limits
/// results in the last limit specified being used. The default limit is
/// 100, with a maximum of 1000 results being returned at a time.
///
/// The maximum number of results to return.
/// A new query with the additional constraint.
public virtual S Limit(int count)
{
return CreateInstance(limit: count);
}
internal virtual S RedirectClassName(String key)
{
return CreateInstance(redirectClassNameForKey: key);
}
#region Where
///
/// Adds a constraint to the query that requires a particular key's value to be
/// contained in the provided list of values.
///
/// The key to check.
/// The values that will match.
/// A new query with the additional constraint.
public virtual S WhereContainedIn(string key, IEnumerable values)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$in", values.ToList()}}}
});
}
///
/// Add a constraint to the querey that requires a particular key's value to be
/// a list containing all of the elements in the provided list of values.
///
/// The key to check.
/// The values that will match.
/// A new query with the additional constraint.
public virtual S WhereContainsAll(string key, IEnumerable values)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$all", values.ToList()}}}
});
}
///
/// Adds a constraint for finding string values that contain a provided string.
/// This will be slow for large data sets.
///
/// The key that the string to match is stored in.
/// The substring that the value must contain.
/// A new query with the additional constraint.
public virtual S WhereContains(string key, string substring)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$regex", RegexQuote(substring)}}}
});
}
///
/// Adds a constraint for finding objects that do not contain a given key.
///
/// The key that should not exist.
/// A new query with the additional constraint.
public virtual S WhereDoesNotExist(string key)
{
return CreateInstance(where: new Dictionary{
{ key, new Dictionary{{"$exists", false}}}
});
}
///
/// Adds a constraint to the query that requires that a particular key's value
/// does not match another AVQuery. This only works on keys whose values are
/// AVObjects or lists of AVObjects.
///
/// The key to check.
/// The query that the value should not match.
/// A new query with the additional constraint.
public virtual S WhereDoesNotMatchQuery(string key, AVQuery query)
where TOther : AVObject
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$notInQuery", query.BuildParameters(true)}}}
});
}
///
/// Adds a constraint for finding string values that end with a provided string.
/// This will be slow for large data sets.
///
/// The key that the string to match is stored in.
/// The substring that the value must end with.
/// A new query with the additional constraint.
public virtual S WhereEndsWith(string key, string suffix)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$regex", RegexQuote(suffix) + "$"}}}
});
}
///
/// Adds a constraint to the query that requires a particular key's value to be
/// equal to the provided value.
///
/// The key to check.
/// The value that the AVObject must contain.
/// A new query with the additional constraint.
public virtual S WhereEqualTo(string key, object value)
{
return CreateInstance(where: new Dictionary {
{ key, value}
});
}
///
/// Adds a constraint to the query that requires a particular key's size to be
/// equal to the provided size.
///
/// The size equal to.
/// The key to check.
/// The value that the size must be.
/// A new query with the additional constraint.
public virtual S WhereSizeEqualTo(string key, uint size)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$size", size}}}
});
}
///
/// Adds a constraint for finding objects that contain a given key.
///
/// The key that should exist.
/// A new query with the additional constraint.
public virtual S WhereExists(string key)
{
return CreateInstance(where: new Dictionary{
{ key, new Dictionary{{"$exists", true}}}
});
}
///
/// Adds a constraint to the query that requires a particular key's value to be
/// greater than the provided value.
///
/// The key to check.
/// The value that provides a lower bound.
/// A new query with the additional constraint.
public virtual S WhereGreaterThan(string key, object value)
{
return CreateInstance(where: new Dictionary{
{ key, new Dictionary{{"$gt", value}}}
});
}
///
/// Adds a constraint to the query that requires a particular key's value to be
/// greater or equal to than the provided value.
///
/// The key to check.
/// The value that provides a lower bound.
/// A new query with the additional constraint.
public virtual S WhereGreaterThanOrEqualTo(string key, object value)
{
return CreateInstance(where: new Dictionary{
{ key, new Dictionary{{"$gte", value}}}
});
}
///
/// Adds a constraint to the query that requires a particular key's value to be
/// less than the provided value.
///
/// The key to check.
/// The value that provides an upper bound.
/// A new query with the additional constraint.
public virtual S WhereLessThan(string key, object value)
{
return CreateInstance(where: new Dictionary{
{ key, new Dictionary{{"$lt", value}}}
});
}
///
/// Adds a constraint to the query that requires a particular key's value to be
/// less than or equal to the provided value.
///
/// The key to check.
/// The value that provides a lower bound.
/// A new query with the additional constraint.
public virtual S WhereLessThanOrEqualTo(string key, object value)
{
return CreateInstance(where: new Dictionary{
{ key, new Dictionary{{"$lte", value}}}
});
}
///
/// Adds a regular expression constraint for finding string values that match the provided
/// regular expression. This may be slow for large data sets.
///
/// The key that the string to match is stored in.
/// The regular expression pattern to match. The Regex must
/// have the options flag set.
/// Any of the following supported PCRE modifiers:
/// i
- Case insensitive search
/// m
Search across multiple lines of input
/// A new query with the additional constraint.
public virtual S WhereMatches(string key, Regex regex, string modifiers)
{
if (!regex.Options.HasFlag(RegexOptions.ECMAScript))
{
throw new ArgumentException(
"Only ECMAScript-compatible regexes are supported. Please use the ECMAScript RegexOptions flag when creating your regex.");
}
return CreateInstance(where: new Dictionary {
{ key, EncodeRegex(regex, modifiers)}
});
}
///
/// Adds a regular expression constraint for finding string values that match the provided
/// regular expression. This may be slow for large data sets.
///
/// The key that the string to match is stored in.
/// The regular expression pattern to match. The Regex must
/// have the options flag set.
/// A new query with the additional constraint.
public virtual S WhereMatches(string key, Regex regex)
{
return WhereMatches(key, regex, null);
}
///
/// Adds a regular expression constraint for finding string values that match the provided
/// regular expression. This may be slow for large data sets.
///
/// The key that the string to match is stored in.
/// The PCRE regular expression pattern to match.
/// Any of the following supported PCRE modifiers:
/// i
- Case insensitive search
/// m
Search across multiple lines of input
/// A new query with the additional constraint.
public virtual S WhereMatches(string key, string pattern, string modifiers = null)
{
return WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers);
}
///
/// Adds a regular expression constraint for finding string values that match the provided
/// regular expression. This may be slow for large data sets.
///
/// The key that the string to match is stored in.
/// The PCRE regular expression pattern to match.
/// A new query with the additional constraint.
public virtual S WhereMatches(string key, string pattern)
{
return WhereMatches(key, pattern, null);
}
///
/// 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.
///
/// The key whose value is being checked.
/// The key in the objects from the subquery to look in.
/// The subquery to run
/// A new query with the additional constraint.
public virtual S WhereMatchesKeyInQuery(string key,
string keyInQuery,
AVQuery query) where TOther : AVObject
{
var parameters = new Dictionary {
{ "query", query.BuildParameters(true)},
{ "key", keyInQuery}
};
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$select", parameters}}}
});
}
///
/// 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.
///
/// The key whose value is being checked.
/// The key in the objects from the subquery to look in.
/// The subquery to run
/// A new query with the additional constraint.
public virtual S WhereDoesNotMatchesKeyInQuery(string key,
string keyInQuery,
AVQuery query) where TOther : AVObject
{
var parameters = new Dictionary {
{ "query", query.BuildParameters(true)},
{ "key", keyInQuery}
};
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$dontSelect", parameters}}}
});
}
///
/// 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.
///
/// The key to check.
/// The query that the value should match.
/// A new query with the additional constraint.
public virtual S WhereMatchesQuery(string key, AVQuery query)
where TOther : AVObject
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$inQuery", query.BuildParameters(true)}}}
});
}
///
/// Adds a proximity-based constraint for finding objects with keys whose GeoPoint
/// values are near the given point.
///
/// The key that the AVGeoPoint is stored in.
/// The reference AVGeoPoint.
/// A new query with the additional constraint.
public virtual S WhereNear(string key, AVGeoPoint point)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$nearSphere", point}}}
});
}
///
/// Adds a constraint to the query that requires a particular key's value to be
/// contained in the provided list of values.
///
/// The key to check.
/// The values that will match.
/// A new query with the additional constraint.
public virtual S WhereNotContainedIn(string key, IEnumerable values)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$nin", values.ToList()}}}
});
}
///
/// Adds a constraint to the query that requires a particular key's value not
/// to be equal to the provided value.
///
/// The key to check.
/// The value that that must not be equalled.
/// A new query with the additional constraint.
public virtual S WhereNotEqualTo(string key, object value)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$ne", value}}}
});
}
///
/// Adds a constraint for finding string values that start with the provided string.
/// This query will use the backend index, so it will be fast even with large data sets.
///
/// The key that the string to match is stored in.
/// The substring that the value must start with.
/// A new query with the additional constraint.
public virtual S WhereStartsWith(string key, string suffix)
{
return CreateInstance(where: new Dictionary {
{ key, new Dictionary{{"$regex", "^" + RegexQuote(suffix)}}}
});
}
///
/// Add a constraint to the query that requires a particular key's coordinates to be
/// contained within a given rectangular geographic bounding box.
///
/// The key to be constrained.
/// The lower-left inclusive corner of the box.
/// The upper-right inclusive corner of the box.
/// A new query with the additional constraint.
public virtual S WhereWithinGeoBox(string key,
AVGeoPoint southwest,
AVGeoPoint northeast)
{
return this.CreateInstance(where: new Dictionary
{
{
key,
new Dictionary
{
{
"$within",
new Dictionary {
{ "$box", new[] {southwest, northeast}}
}
}
}
}
});
}
///
/// Adds a proximity-based constraint for finding objects with keys whose GeoPoint
/// values are near the given point and within the maximum distance given.
///
/// The key that the AVGeoPoint is stored in.
/// The reference AVGeoPoint.
/// The maximum distance (in radians) of results to return.
/// A new query with the additional constraint.
public virtual S WhereWithinDistance(
string key, AVGeoPoint point, AVGeoDistance maxDistance)
{
var nearWhere = new Dictionary {
{ key, new Dictionary{{"$nearSphere", point}}}
};
var mergedWhere = MergeWhere(nearWhere, new Dictionary {
{ key, new Dictionary{{"$maxDistance", maxDistance.Radians}}}
});
return CreateInstance(where: mergedWhere);
}
internal virtual S WhereRelatedTo(AVObject parent, string key)
{
return CreateInstance(where: new Dictionary {
{
"$relatedTo",
new Dictionary {
{ "object", parent},
{ "key", key}
}
}
});
}
#endregion
///
/// Retrieves a list of AVObjects that satisfy this query from LeanCloud.
///
/// The list of AVObjects that match this query.
public virtual Task> FindAsync()
{
return FindAsync(CancellationToken.None);
}
///
/// Retrieves a list of AVObjects that satisfy this query from LeanCloud.
///
/// The cancellation token.
/// The list of AVObjects that match this query.
public abstract Task> FindAsync(CancellationToken cancellationToken);
///
/// Retrieves at most one AVObject that satisfies this query.
///
/// A single AVObject that satisfies this query, or else null.
public virtual Task FirstOrDefaultAsync()
{
return FirstOrDefaultAsync(CancellationToken.None);
}
///
/// Retrieves at most one AVObject that satisfies this query.
///
/// The cancellation token.
/// A single AVObject that satisfies this query, or else null.
public abstract Task FirstOrDefaultAsync(CancellationToken cancellationToken);
///
/// Retrieves at most one AVObject that satisfies this query.
///
/// A single AVObject that satisfies this query.
/// If no results match the query.
public virtual Task FirstAsync()
{
return FirstAsync(CancellationToken.None);
}
///
/// Retrieves at most one AVObject that satisfies this query.
///
/// The cancellation token.
/// A single AVObject that satisfies this query.
/// If no results match the query.
public abstract Task FirstAsync(CancellationToken cancellationToken);
///
/// Counts the number of objects that match this query.
///
/// The number of objects that match this query.
public virtual Task CountAsync()
{
return CountAsync(CancellationToken.None);
}
///
/// Counts the number of objects that match this query.
///
/// The cancellation token.
/// The number of objects that match this query.
public abstract Task CountAsync(CancellationToken cancellationToken);
///
/// Constructs a AVObject whose id is already known by fetching data
/// from the server.
///
/// ObjectId of the AVObject to fetch.
/// The AVObject for the given objectId.
public virtual Task GetAsync(string objectId)
{
return GetAsync(objectId, CancellationToken.None);
}
///
/// Constructs a AVObject whose id is already known by fetching data
/// from the server.
///
/// ObjectId of the AVObject to fetch.
/// The cancellation token.
/// The AVObject for the given objectId.
public abstract Task GetAsync(string objectId, CancellationToken cancellationToken);
internal object GetConstraint(string key)
{
return where == null ? null : where.GetOrDefault(key, null);
}
///
/// 构建查询字符串
///
/// 是否包含 ClassName
///
public IDictionary BuildParameters(bool includeClassName = false)
{
Dictionary result = new Dictionary();
if (where != null)
{
result["where"] = PointerOrLocalIdEncoder.Instance.Encode(where);
}
if (orderBy != null)
{
result["order"] = string.Join(",", orderBy.ToArray());
}
if (skip != null)
{
result["skip"] = skip.Value;
}
if (limit != null)
{
result["limit"] = limit.Value;
}
if (includes != null)
{
result["include"] = string.Join(",", includes.ToArray());
}
if (selectedKeys != null)
{
result["keys"] = string.Join(",", selectedKeys.ToArray());
}
if (includeClassName)
{
result["className"] = className;
}
if (redirectClassNameForKey != null)
{
result["redirectClassNameForKey"] = redirectClassNameForKey;
}
return result;
}
private string RegexQuote(string input)
{
return "\\Q" + input.Replace("\\E", "\\E\\\\E\\Q") + "\\E";
}
private string GetRegexOptions(Regex regex, string modifiers)
{
string result = modifiers ?? "";
if (regex.Options.HasFlag(RegexOptions.IgnoreCase) && !modifiers.Contains("i"))
{
result += "i";
}
if (regex.Options.HasFlag(RegexOptions.Multiline) && !modifiers.Contains("m"))
{
result += "m";
}
return result;
}
private IDictionary EncodeRegex(Regex regex, string modifiers)
{
var options = GetRegexOptions(regex, modifiers);
var dict = new Dictionary();
dict["$regex"] = regex.ToString();
if (!string.IsNullOrEmpty(options))
{
dict["$options"] = options;
}
return dict;
}
}
//public abstract class AVQueryBase : IAVQueryTuple
// where T : IAVObject
//{
// protected readonly string className;
// protected readonly Dictionary where;
// protected readonly ReadOnlyCollection orderBy;
// protected readonly ReadOnlyCollection includes;
// protected readonly ReadOnlyCollection selectedKeys;
// protected readonly String redirectClassNameForKey;
// protected readonly int? skip;
// protected readonly int? limit;
// internal string ClassName { get { return className; } }
// private string relativeUri;
// internal string RelativeUri
// {
// get
// {
// string rtn = string.Empty;
// if (string.IsNullOrEmpty(relativeUri))
// {
// rtn = "classes/" + Uri.EscapeDataString(this.className);
// }
// else
// {
// rtn = relativeUri;
// }
// return rtn;
// }
// set
// {
// relativeUri = value;
// }
// }
// public Dictionary Condition
// {
// get { return this.where; }
// }
// protected AVQueryBase()
// {
// }
// internal abstract S CreateInstance(AVQueryBase source,
// IDictionary where = null,
// IEnumerable replacementOrderBy = null,
// IEnumerable thenBy = null,
// int? skip = null,
// int? limit = null,
// IEnumerable includes = null,
// IEnumerable selectedKeys = null,
// String redirectClassNameForKey = null);
// ///
// /// 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.
// ///
// protected AVQueryBase(AVQueryBase source,
// IDictionary where = null,
// IEnumerable replacementOrderBy = null,
// IEnumerable thenBy = null,
// int? skip = null,
// int? limit = null,
// IEnumerable includes = null,
// IEnumerable selectedKeys = null,
// String redirectClassNameForKey = 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;
// this.redirectClassNameForKey = source.redirectClassNameForKey;
// if (where != null)
// {
// var newWhere = MergeWhereClauses(where);
// this.where = new Dictionary(newWhere);
// }
// if (replacementOrderBy != null)
// {
// this.orderBy = new ReadOnlyCollection(replacementOrderBy.ToList());
// }
// if (thenBy != null)
// {
// if (this.orderBy == null)
// {
// throw new ArgumentException("You must call OrderBy before calling ThenBy.");
// }
// var newOrderBy = new List(this.orderBy);
// newOrderBy.AddRange(thenBy);
// this.orderBy = new ReadOnlyCollection(newOrderBy);
// }
// // Remove duplicates.
// if (this.orderBy != null)
// {
// var newOrderBy = new HashSet(this.orderBy);
// this.orderBy = new ReadOnlyCollection(newOrderBy.ToList());
// }
// 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(newIncludes.ToList());
// }
// if (selectedKeys != null)
// {
// var newSelectedKeys = MergeSelectedKeys(selectedKeys);
// this.selectedKeys = new ReadOnlyCollection(newSelectedKeys.ToList());
// }
// if (redirectClassNameForKey != null)
// {
// this.redirectClassNameForKey = redirectClassNameForKey;
// }
// }
// public AVQueryBase(string className)
// {
// if (string.IsNullOrEmpty(className))
// {
// throw new ArgumentNullException("className", "Must specify a AVObject class name when creating a AVQuery.");
// }
// this.className = className;
// }
// private HashSet MergeIncludes(IEnumerable includes)
// {
// if (this.includes == null)
// {
// return new HashSet(includes);
// }
// var newIncludes = new HashSet(this.includes);
// foreach (var item in includes)
// {
// newIncludes.Add(item);
// }
// return newIncludes;
// }
// private HashSet MergeSelectedKeys(IEnumerable selectedKeys)
// {
// if (this.selectedKeys == null)
// {
// return new HashSet(selectedKeys);
// }
// var newSelectedKeys = new HashSet(this.selectedKeys);
// foreach (var item in selectedKeys)
// {
// newSelectedKeys.Add(item);
// }
// return newSelectedKeys;
// }
// private IDictionary MergeWhereClauses(IDictionary where)
// {
// return MergeWhere(this.where, where);
// }
// public virtual IDictionary MergeWhere(IDictionary primary, IDictionary secondary)
// {
// if (secondary == null)
// {
// return primary;
// }
// var newWhere = new Dictionary(primary);
// foreach (var pair in secondary)
// {
// var condition = pair.Value as IDictionary;
// if (newWhere.ContainsKey(pair.Key))
// {
// var oldCondition = newWhere[pair.Key] as IDictionary;
// if (oldCondition == null || condition == null)
// {
// throw new ArgumentException("More than one where clause for the given key provided.");
// }
// var newCondition = new Dictionary(oldCondition);
// foreach (var conditionPair in condition)
// {
// if (newCondition.ContainsKey(conditionPair.Key))
// {
// throw new ArgumentException("More than one condition for the given key provided.");
// }
// newCondition[conditionPair.Key] = conditionPair.Value;
// }
// newWhere[pair.Key] = newCondition;
// }
// else
// {
// newWhere[pair.Key] = pair.Value;
// }
// }
// return newWhere;
// }
// /////
// ///// Constructs a query that is the or of the given queries.
// /////
// ///// The list of AVQueries to 'or' together.
// ///// A AVQquery that is the 'or' of the passed in queries.
// //public static AVQuery Or(IEnumerable> queries)
// //{
// // string className = null;
// // var orValue = new List>();
// // // We need to cast it to non-generic IEnumerable because of AOT-limitation
// // var nonGenericQueries = (IEnumerable)queries;
// // foreach (var obj in nonGenericQueries)
// // {
// // var q = (AVQuery)obj;
// // if (className != null && q.className != className)
// // {
// // throw new ArgumentException(
// // "All of the queries in an or query must be on the same class.");
// // }
// // className = q.className;
// // var parameters = q.BuildParameters();
// // if (parameters.Count == 0)
// // {
// // continue;
// // }
// // object where;
// // if (!parameters.TryGetValue("where", out where) || parameters.Count > 1)
// // {
// // throw new ArgumentException(
// // "None of the queries in an or query can have non-filtering clauses");
// // }
// // orValue.Add(where as IDictionary);
// // }
// // return new AVQuery(new AVQuery(className),
// // where: new Dictionary {
// // {"$or", orValue}
// // });
// //}
// #region Order By
// ///
// /// Sorts the results in ascending order by the given key.
// /// This will override any existing ordering for the query.
// ///
// /// The key to order by.
// /// A new query with the additional constraint.
// public virtual S OrderBy(string key)
// {
// return CreateInstance( replacementOrderBy: new List { key });
// }
// ///
// /// Sorts the results in descending order by the given key.
// /// This will override any existing ordering for the query.
// ///
// /// The key to order by.
// /// A new query with the additional constraint.
// public virtual S OrderByDescending(string key)
// {
// return CreateInstance( replacementOrderBy: new List { "-" + key });
// }
// ///
// /// Sorts the results in ascending order by the given key, after previous
// /// ordering has been applied.
// ///
// /// This method can only be called if there is already an
// /// or
// /// on this query.
// ///
// /// The key to order by.
// /// A new query with the additional constraint.
// public virtual S ThenBy(string key)
// {
// return CreateInstance( thenBy: new List { key });
// }
// ///
// /// Sorts the results in descending order by the given key, after previous
// /// ordering has been applied.
// ///
// /// This method can only be called if there is already an
// /// or on this query.
// ///
// /// The key to order by.
// /// A new query with the additional constraint.
// public virtual S ThenByDescending(string key)
// {
// return CreateInstance( thenBy: new List { "-" + key });
// }
// #endregion
// ///
// /// Include nested AVObjects for the provided key. You can use dot notation
// /// to specify which fields in the included objects should also be fetched.
// ///
// /// The key that should be included.
// /// A new query with the additional constraint.
// public virtual S Include(string key)
// {
// return CreateInstance( includes: new List { key });
// }
// ///
// /// Restrict the fields of returned AVObjects to only include the provided key.
// /// If this is called multiple times, then all of the keys specified in each of
// /// the calls will be included.
// ///
// /// The key that should be included.
// /// A new query with the additional constraint.
// public virtual S Select(string key)
// {
// return CreateInstance( selectedKeys: new List { key });
// }
// ///
// /// Skips a number of results before returning. This is useful for pagination
// /// of large queries. Chaining multiple skips together will cause more results
// /// to be skipped.
// ///
// /// The number of results to skip.
// /// A new query with the additional constraint.
// public virtual S Skip(int count)
// {
// return CreateInstance( skip: count);
// }
// ///
// /// Controls the maximum number of results that are returned. Setting a negative
// /// limit denotes retrieval without a limit. Chaining multiple limits
// /// results in the last limit specified being used. The default limit is
// /// 100, with a maximum of 1000 results being returned at a time.
// ///
// /// The maximum number of results to return.
// /// A new query with the additional constraint.
// public virtual S Limit(int count)
// {
// return CreateInstance( limit: count);
// }
// internal virtual S RedirectClassName(String key)
// {
// return CreateInstance( redirectClassNameForKey: key);
// }
// #region Where
// ///
// /// Adds a constraint to the query that requires a particular key's value to be
// /// contained in the provided list of values.
// ///
// /// The key to check.
// /// The values that will match.
// /// A new query with the additional constraint.
// public virtual S WhereContainedIn(string key, IEnumerable values)
// {
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$in", values.ToList()}}}
// });
// }
// ///
// /// Add a constraint to the querey that requires a particular key's value to be
// /// a list containing all of the elements in the provided list of values.
// ///
// /// The key to check.
// /// The values that will match.
// /// A new query with the additional constraint.
// public virtual S WhereContainsAll(string key, IEnumerable values)
// {
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$all", values.ToList()}}}
// });
// }
// ///
// /// Adds a constraint for finding string values that contain a provided string.
// /// This will be slow for large data sets.
// ///
// /// The key that the string to match is stored in.
// /// The substring that the value must contain.
// /// A new query with the additional constraint.
// public virtual S WhereContains(string key, string substring)
// {
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$regex", RegexQuote(substring)}}}
// });
// }
// ///
// /// Adds a constraint for finding objects that do not contain a given key.
// ///
// /// The key that should not exist.
// /// A new query with the additional constraint.
// public virtual S WhereDoesNotExist(string key)
// {
// return CreateInstance( where: new Dictionary{
// { key, new Dictionary{{"$exists", false}}}
// });
// }
// ///
// /// Adds a constraint to the query that requires that a particular key's value
// /// does not match another AVQuery. This only works on keys whose values are
// /// AVObjects or lists of AVObjects.
// ///
// /// The key to check.
// /// The query that the value should not match.
// /// A new query with the additional constraint.
// public virtual S WhereDoesNotMatchQuery(string key, AVQuery query)
// where TOther : AVObject
// {
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$notInQuery", query.BuildParameters(true)}}}
// });
// }
// ///
// /// Adds a constraint for finding string values that end with a provided string.
// /// This will be slow for large data sets.
// ///
// /// The key that the string to match is stored in.
// /// The substring that the value must end with.
// /// A new query with the additional constraint.
// public virtual S WhereEndsWith(string key, string suffix)
// {
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$regex", RegexQuote(suffix) + "$"}}}
// });
// }
// ///
// /// Adds a constraint to the query that requires a particular key's value to be
// /// equal to the provided value.
// ///
// /// The key to check.
// /// The value that the AVObject must contain.
// /// A new query with the additional constraint.
// public virtual S WhereEqualTo(string key, object value)
// {
// return CreateInstance( where: new Dictionary {
// { key, value}
// });
// }
// ///
// /// Adds a constraint to the query that requires a particular key's size to be
// /// equal to the provided size.
// ///
// /// The size equal to.
// /// The key to check.
// /// The value that the size must be.
// /// A new query with the additional constraint.
// public virtual S WhereSizeEqualTo(string key, uint size)
// {
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$size", size}}}
// });
// }
// ///
// /// Adds a constraint for finding objects that contain a given key.
// ///
// /// The key that should exist.
// /// A new query with the additional constraint.
// public virtual S WhereExists(string key)
// {
// return CreateInstance( where: new Dictionary{
// { key, new Dictionary{{"$exists", true}}}
// });
// }
// ///
// /// Adds a constraint to the query that requires a particular key's value to be
// /// greater than the provided value.
// ///
// /// The key to check.
// /// The value that provides a lower bound.
// /// A new query with the additional constraint.
// public virtual S WhereGreaterThan(string key, object value)
// {
// return CreateInstance( where: new Dictionary{
// { key, new Dictionary{{"$gt", value}}}
// });
// }
// ///
// /// Adds a constraint to the query that requires a particular key's value to be
// /// greater or equal to than the provided value.
// ///
// /// The key to check.
// /// The value that provides a lower bound.
// /// A new query with the additional constraint.
// public virtual S WhereGreaterThanOrEqualTo(string key, object value)
// {
// return CreateInstance( where: new Dictionary{
// { key, new Dictionary{{"$gte", value}}}
// });
// }
// ///
// /// Adds a constraint to the query that requires a particular key's value to be
// /// less than the provided value.
// ///
// /// The key to check.
// /// The value that provides an upper bound.
// /// A new query with the additional constraint.
// public virtual S WhereLessThan(string key, object value)
// {
// return CreateInstance( where: new Dictionary{
// { key, new Dictionary{{"$lt", value}}}
// });
// }
// ///
// /// Adds a constraint to the query that requires a particular key's value to be
// /// less than or equal to the provided value.
// ///
// /// The key to check.
// /// The value that provides a lower bound.
// /// A new query with the additional constraint.
// public virtual S WhereLessThanOrEqualTo(string key, object value)
// {
// return CreateInstance( where: new Dictionary{
// { key, new Dictionary{{"$lte", value}}}
// });
// }
// ///
// /// Adds a regular expression constraint for finding string values that match the provided
// /// regular expression. This may be slow for large data sets.
// ///
// /// The key that the string to match is stored in.
// /// The regular expression pattern to match. The Regex must
// /// have the options flag set.
// /// Any of the following supported PCRE modifiers:
// /// i
- Case insensitive search
// /// m
Search across multiple lines of input
// /// A new query with the additional constraint.
// public virtual S WhereMatches(string key, Regex regex, string modifiers)
// {
// if (!regex.Options.HasFlag(RegexOptions.ECMAScript))
// {
// throw new ArgumentException(
// "Only ECMAScript-compatible regexes are supported. Please use the ECMAScript RegexOptions flag when creating your regex.");
// }
// return CreateInstance( where: new Dictionary {
// { key, EncodeRegex(regex, modifiers)}
// });
// }
// ///
// /// Adds a regular expression constraint for finding string values that match the provided
// /// regular expression. This may be slow for large data sets.
// ///
// /// The key that the string to match is stored in.
// /// The regular expression pattern to match. The Regex must
// /// have the options flag set.
// /// A new query with the additional constraint.
// public virtual S WhereMatches(string key, Regex regex)
// {
// return WhereMatches(key, regex, null);
// }
// ///
// /// Adds a regular expression constraint for finding string values that match the provided
// /// regular expression. This may be slow for large data sets.
// ///
// /// The key that the string to match is stored in.
// /// The PCRE regular expression pattern to match.
// /// Any of the following supported PCRE modifiers:
// /// i
- Case insensitive search
// /// m
Search across multiple lines of input
// /// A new query with the additional constraint.
// public virtual S WhereMatches(string key, string pattern, string modifiers = null)
// {
// return WhereMatches(key, new Regex(pattern, RegexOptions.ECMAScript), modifiers);
// }
// ///
// /// Adds a regular expression constraint for finding string values that match the provided
// /// regular expression. This may be slow for large data sets.
// ///
// /// The key that the string to match is stored in.
// /// The PCRE regular expression pattern to match.
// /// A new query with the additional constraint.
// public virtual S WhereMatches(string key, string pattern)
// {
// return WhereMatches(key, pattern, null);
// }
// ///
// /// 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.
// ///
// /// The key whose value is being checked.
// /// The key in the objects from the subquery to look in.
// /// The subquery to run
// /// A new query with the additional constraint.
// public virtual S WhereMatchesKeyInQuery(string key,
// string keyInQuery,
// AVQuery query) where TOther : AVObject
// {
// var parameters = new Dictionary {
// { "query", query.BuildParameters(true)},
// { "key", keyInQuery}
// };
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$select", parameters}}}
// });
// }
// ///
// /// 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.
// ///
// /// The key whose value is being checked.
// /// The key in the objects from the subquery to look in.
// /// The subquery to run
// /// A new query with the additional constraint.
// public virtual S WhereDoesNotMatchesKeyInQuery(string key,
// string keyInQuery,
// AVQuery query) where TOther : AVObject
// {
// var parameters = new Dictionary {
// { "query", query.BuildParameters(true)},
// { "key", keyInQuery}
// };
// return CreateInstance( where: new Dictionary {
// { key, new Dictionary{{"$dontSelect", parameters}}}
// });
// }
// ///
// /// 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.
// ///
// /// The key to check.
// /// The query that the value should match.
// /// A new query with the additional constraint.
// public virtual S WhereMatchesQuery(string key, AVQuery query)
// where TOther : AVObject
// {
// return CreateInstance( where: new Dictionary