feat: full text search
parent
ab60fb227b
commit
e657a6ec69
|
@ -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<Account> query = new LCSearchQuery<Account>("Account");
|
||||
query.QueryString("*")
|
||||
.OrderByDescending("balance")
|
||||
.Limit(200);
|
||||
LCSearchResponse<Account> 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<Account> query = new LCSearchQuery<Account>("Account");
|
||||
LCSearchSortBuilder sortBuilder = new LCSearchSortBuilder();
|
||||
sortBuilder.OrderByAscending("balance");
|
||||
query.QueryString("*")
|
||||
.SortBy(sortBuilder)
|
||||
.Limit(200);
|
||||
LCSearchResponse<Account> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> where T : LCObject {
|
||||
private string className;
|
||||
private string queryString;
|
||||
private IEnumerable<string> highlights;
|
||||
private IEnumerable<string> includeKeys;
|
||||
private int limit;
|
||||
private int skip;
|
||||
private string sid;
|
||||
private List<string> orders;
|
||||
private LCSearchSortBuilder sortBuilder;
|
||||
|
||||
public LCSearchQuery(string className) {
|
||||
this.className = className;
|
||||
limit = 100;
|
||||
skip = 0;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> QueryString(string q) {
|
||||
queryString = q;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> Highlights(IEnumerable<string> highlights) {
|
||||
this.highlights = highlights;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> Include(IEnumerable<string> keys) {
|
||||
includeKeys = keys;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> Limit(int amount) {
|
||||
limit = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> Skip(int amount) {
|
||||
skip = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> Sid(string sid) {
|
||||
this.sid = sid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> OrderByAscending(string key) {
|
||||
orders = new List<string>();
|
||||
orders.Add(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> OrderByDescending(string key) {
|
||||
return OrderByAscending($"-{key}");
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> AddAscendingOrder(string key) {
|
||||
if (orders == null) {
|
||||
orders = new List<string>();
|
||||
}
|
||||
orders.Add(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> AddDescendingOrder(string key) {
|
||||
return AddAscendingOrder($"-{key}");
|
||||
}
|
||||
|
||||
public LCSearchQuery<T> SortBy(LCSearchSortBuilder sortBuilder) {
|
||||
this.sortBuilder = sortBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<LCSearchResponse<T>> Find() {
|
||||
string path = "search/select";
|
||||
Dictionary<string, object> queryParams = new Dictionary<string, object> {
|
||||
{ "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<string, object> response = await LCCore.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams);
|
||||
LCSearchResponse<T> ret = new LCSearchResponse<T>();
|
||||
ret.Hits = (int)response["hits"];
|
||||
ret.Sid = (string)response["sid"];
|
||||
List<object> results = response["results"] as List<object>;
|
||||
List<T> list = new List<T>();
|
||||
foreach (object data in results) {
|
||||
LCObjectData objectData = LCObjectData.Decode(data as Dictionary<string, object>);
|
||||
T obj = LCObject.Create(className) as T;
|
||||
obj.Merge(objectData);
|
||||
list.Add(obj);
|
||||
}
|
||||
ret.Results = list.AsReadOnly();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCSearchResponse<T> where T : LCObject {
|
||||
public int Hits {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<T> Results {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string Sid {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System.Collections.Generic;
|
||||
using LC.Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCSearchSortBuilder {
|
||||
private List<object> fields;
|
||||
|
||||
public LCSearchSortBuilder() {
|
||||
fields = new List<object>();
|
||||
}
|
||||
|
||||
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<string, object> {
|
||||
{ "_geo_distance", new Dictionary<string, object> {
|
||||
{ key, new Dictionary<string, object> {
|
||||
{ "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<string, object> {
|
||||
{ key, new Dictionary<string, object> {
|
||||
{ "order", order ?? "asc" },
|
||||
{ "mode", mode ?? "avg" },
|
||||
{ "missing", $"_{missing ?? "last"}" }
|
||||
} }
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
internal string Build() {
|
||||
return JsonConvert.SerializeObject(fields);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue