diff --git a/Storage/Storage.Test/FullTextSearchTest.cs b/Storage/Storage.Test/FullTextSearchTest.cs new file mode 100644 index 0000000..cfcd2bc --- /dev/null +++ b/Storage/Storage.Test/FullTextSearchTest.cs @@ -0,0 +1,39 @@ +using NUnit.Framework; +using System.Threading.Tasks; +using LeanCloud.Storage; + +namespace Storage.Test { + public class FullTextSearchTest : BaseTest { + [Test] + public async Task QueryByOrder() { + LCSearchQuery query = new LCSearchQuery("Account"); + query.QueryString("*") + .OrderByDescending("balance") + .Limit(200); + LCSearchResponse response = await query.Find(); + Assert.Greater(response.Hits, 0); + for (int i = 0; i < response.Results.Count - 1; i++) { + int b1 = response.Results[i].Balance; + int b2 = response.Results[i + 1].Balance; + Assert.GreaterOrEqual(b1, b2); + } + } + + [Test] + public async Task QueryBySort() { + LCSearchQuery query = new LCSearchQuery("Account"); + LCSearchSortBuilder sortBuilder = new LCSearchSortBuilder(); + sortBuilder.OrderByAscending("balance"); + query.QueryString("*") + .SortBy(sortBuilder) + .Limit(200); + LCSearchResponse response = await query.Find(); + Assert.Greater(response.Hits, 0); + for (int i = 0; i < response.Results.Count - 1; i++) { + int b1 = response.Results[i].Balance; + int b2 = response.Results[i + 1].Balance; + Assert.LessOrEqual(b1, b2); + } + } + } +} diff --git a/Storage/Storage/Public/LCSearchQuery.cs b/Storage/Storage/Public/LCSearchQuery.cs new file mode 100644 index 0000000..7fb3c41 --- /dev/null +++ b/Storage/Storage/Public/LCSearchQuery.cs @@ -0,0 +1,125 @@ +using System.Linq; +using System.Threading.Tasks; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using LeanCloud.Common; +using LeanCloud.Storage.Internal.Object; + +namespace LeanCloud.Storage { + public class LCSearchQuery where T : LCObject { + private string className; + private string queryString; + private IEnumerable highlights; + private IEnumerable includeKeys; + private int limit; + private int skip; + private string sid; + private List orders; + private LCSearchSortBuilder sortBuilder; + + public LCSearchQuery(string className) { + this.className = className; + limit = 100; + skip = 0; + } + + public LCSearchQuery QueryString(string q) { + queryString = q; + return this; + } + + public LCSearchQuery Highlights(IEnumerable highlights) { + this.highlights = highlights; + return this; + } + + public LCSearchQuery Include(IEnumerable keys) { + includeKeys = keys; + return this; + } + + public LCSearchQuery Limit(int amount) { + limit = amount; + return this; + } + + public LCSearchQuery Skip(int amount) { + skip = amount; + return this; + } + + public LCSearchQuery Sid(string sid) { + this.sid = sid; + return this; + } + + public LCSearchQuery OrderByAscending(string key) { + orders = new List(); + orders.Add(key); + return this; + } + + public LCSearchQuery OrderByDescending(string key) { + return OrderByAscending($"-{key}"); + } + + public LCSearchQuery AddAscendingOrder(string key) { + if (orders == null) { + orders = new List(); + } + orders.Add(key); + return this; + } + + public LCSearchQuery AddDescendingOrder(string key) { + return AddAscendingOrder($"-{key}"); + } + + public LCSearchQuery SortBy(LCSearchSortBuilder sortBuilder) { + this.sortBuilder = sortBuilder; + return this; + } + + public async Task> Find() { + string path = "search/select"; + Dictionary queryParams = new Dictionary { + { "clazz", className }, + { "limit", limit }, + { "skip", skip } + }; + if (queryString != null) { + queryParams["q"] = queryString; + } + if (highlights != null && highlights.Count() > 0) { + queryParams["highlights"] = string.Join(",", highlights); + } + if (includeKeys != null && includeKeys.Count() > 0) { + queryParams["include"] = string.Join(",", includeKeys); + } + if (!string.IsNullOrEmpty(sid)) { + queryParams["sid"] = sid; + } + if (orders != null && orders.Count() > 0) { + queryParams["order"] = string.Join(",", orders); + } + if (sortBuilder != null) { + queryParams["sort"] = sortBuilder.Build(); + } + + Dictionary response = await LCCore.HttpClient.Get>(path, queryParams: queryParams); + LCSearchResponse ret = new LCSearchResponse(); + ret.Hits = (int)response["hits"]; + ret.Sid = (string)response["sid"]; + List results = response["results"] as List; + List list = new List(); + foreach (object data in results) { + LCObjectData objectData = LCObjectData.Decode(data as Dictionary); + T obj = LCObject.Create(className) as T; + obj.Merge(objectData); + list.Add(obj); + } + ret.Results = list.AsReadOnly(); + return ret; + } + } +} diff --git a/Storage/Storage/Public/LCSearchResponse.cs b/Storage/Storage/Public/LCSearchResponse.cs new file mode 100644 index 0000000..066c50d --- /dev/null +++ b/Storage/Storage/Public/LCSearchResponse.cs @@ -0,0 +1,17 @@ +using System.Collections.ObjectModel; + +namespace LeanCloud.Storage { + public class LCSearchResponse where T : LCObject { + public int Hits { + get; set; + } + + public ReadOnlyCollection Results { + get; set; + } + + public string Sid { + get; set; + } + } +} diff --git a/Storage/Storage/Public/LCSearchSortBuilder.cs b/Storage/Storage/Public/LCSearchSortBuilder.cs new file mode 100644 index 0000000..0f2d043 --- /dev/null +++ b/Storage/Storage/Public/LCSearchSortBuilder.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using LC.Newtonsoft.Json; + +namespace LeanCloud.Storage { + public class LCSearchSortBuilder { + private List fields; + + public LCSearchSortBuilder() { + fields = new List(); + } + + public LCSearchSortBuilder OrderByAscending(string key, string mode = null, string missing = null) { + return AddField(key, "asc", mode, missing); + } + + public LCSearchSortBuilder OrderByDescending(string key, string mode = null, string missing = null) { + return AddField(key, "desc", mode, missing); + } + + public LCSearchSortBuilder WhereNear(string key, LCGeoPoint point, + string order = null, string mode = null, string unit = null) { + fields.Add(new Dictionary { + { "_geo_distance", new Dictionary { + { key, new Dictionary { + { "lat", point.Latitude }, + { "lon", point.Longitude } + } }, + { "order", order ?? "asc" }, + { "mode", mode ?? "avg" }, + { "unit", unit ?? "km" } + } } + }); + return this; + } + + private LCSearchSortBuilder AddField(string key, string order = null, string mode = null, string missing = null) { + fields.Add(new Dictionary { + { key, new Dictionary { + { "order", order ?? "asc" }, + { "mode", mode ?? "avg" }, + { "missing", $"_{missing ?? "last"}" } + } } + }); + return this; + } + + internal string Build() { + return JsonConvert.SerializeObject(fields); + } + } +}