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