Skip to content

Commit eba20f8

Browse files
committed
NRediSearch => SE.Redis.Modules; implement CL.THROTTLE
1 parent 69897ca commit eba20f8

File tree

9 files changed

+1014
-1
lines changed

9 files changed

+1014
-1
lines changed

NRediSearch/NRediSearch.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<TargetFrameworks>$(LibraryTargetFrameworks)</TargetFrameworks>
55
<VersionPrefix>0.1</VersionPrefix>
66
<GenerateDocumentationFile>false</GenerateDocumentationFile>
7-
<PackageTags>Redis;Search;RediSearch</PackageTags>
7+
<PackageTags>Redis;Search;Modules;RediSearch</PackageTags>
88
</PropertyGroup>
99
<ItemGroup>
1010
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj" />

StackExchange.Redis.Modules/RediSearch/Client.cs

Lines changed: 420 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// .NET port of https://github.com/RedisLabs/JRediSearch/
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using StackExchange.Redis;
9+
10+
namespace StackExchange.Redis.Modules.RediSearch
11+
{
12+
/// <summary>
13+
/// Document represents a single indexed document or entity in the engine
14+
/// </summary>
15+
public class Document
16+
{
17+
18+
public string Id { get; }
19+
public double Score { get; }
20+
public byte[] Payload { get; }
21+
private Dictionary<String, RedisValue> properties = new Dictionary<string, RedisValue>();
22+
23+
public Document(string id, double score, byte[] payload)
24+
{
25+
Id = id;
26+
Score = score;
27+
Payload = payload;
28+
}
29+
30+
public static Document Load(string id, double score, byte[] payload, RedisValue[] fields)
31+
{
32+
Document ret = new Document(id, score, payload);
33+
if (fields != null)
34+
{
35+
for (int i = 0; i < fields.Length; i += 2)
36+
{
37+
ret[(string)fields[i]] = fields[i + 1];
38+
}
39+
}
40+
return ret;
41+
}
42+
43+
public RedisValue this[string key]
44+
{
45+
get { return properties.TryGetValue(key, out var val) ? val : default(RedisValue); }
46+
internal set { properties[key] = value; }
47+
}
48+
49+
public bool HasProperty(string key) => properties.ContainsKey(key);
50+
}
51+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using StackExchange.Redis;
2+
using System.Collections;
3+
namespace StackExchange.Redis.Modules.RediSearch
4+
{
5+
/// <summary>
6+
/// Cache to ensure we encode and box literals once only
7+
/// </summary>
8+
internal static class Literals
9+
{
10+
private static Hashtable _boxed = new Hashtable();
11+
private static object _null = RedisValue.Null;
12+
/// <summary>
13+
/// Obtain a lazily-cached pre-encoded and boxed representation of a string
14+
/// </summary>
15+
/// <remarks>This shoul donly be used for fixed values, not user data (the cache is never reclaimed, so it will be a memory leak)</remarks>
16+
public static object Literal(this string value)
17+
{
18+
if (value == null) return _null;
19+
20+
object boxed = _boxed[value];
21+
if (boxed == null)
22+
{
23+
lock (_boxed)
24+
{
25+
boxed = _boxed[value];
26+
if (boxed == null)
27+
{
28+
boxed = (RedisValue)value;
29+
_boxed.Add(value, boxed);
30+
}
31+
}
32+
}
33+
return boxed;
34+
}
35+
}
36+
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// .NET port of https://github.com/RedisLabs/JRediSearch/
2+
3+
using StackExchange.Redis;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Globalization;
7+
8+
namespace StackExchange.Redis.Modules.RediSearch
9+
{
10+
/// <summary>
11+
/// Query represents query parameters and filters to load results from the engine
12+
/// </summary>
13+
public class Query
14+
{
15+
/// <summary>
16+
/// Filter represents a filtering rules in a query
17+
/// </summary>
18+
public abstract class Filter
19+
{
20+
21+
public string Property { get; }
22+
23+
internal abstract void SerializeRedisArgs(List<object> args);
24+
25+
internal Filter(string property)
26+
{
27+
Property = property;
28+
}
29+
30+
}
31+
32+
/// <summary>
33+
/// NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive
34+
/// </summary>
35+
public class NumericFilter : Filter
36+
{
37+
38+
private readonly double min, max;
39+
private readonly bool exclusiveMin, exclusiveMax;
40+
41+
public NumericFilter(string property, double min, bool exclusiveMin, double max, bool exclusiveMax) : base(property)
42+
{
43+
this.min = min;
44+
this.max = max;
45+
this.exclusiveMax = exclusiveMax;
46+
this.exclusiveMin = exclusiveMin;
47+
}
48+
49+
public NumericFilter(string property, double min, double max) : this(property, min, false, max, false) { }
50+
51+
52+
internal override void SerializeRedisArgs(List<object> args)
53+
{
54+
RedisValue FormatNum(double num, bool exclude)
55+
{
56+
if (!exclude || double.IsInfinity(num))
57+
{
58+
return (RedisValue)num; // can use directly
59+
}
60+
// need to add leading bracket
61+
return "(" + num.ToString("G17", NumberFormatInfo.InvariantInfo);
62+
}
63+
args.Add("FILTER".Literal());
64+
args.Add(Property);
65+
args.Add(FormatNum(min, exclusiveMin));
66+
args.Add(FormatNum(max, exclusiveMax));
67+
}
68+
}
69+
70+
/// <summary>
71+
/// GeoFilter encapsulates a radius filter on a geographical indexed fields
72+
/// </summary>
73+
public class GeoFilter : Filter
74+
{
75+
76+
private readonly double lon, lat, radius;
77+
private GeoUnit unit;
78+
79+
public GeoFilter(string property, double lon, double lat, double radius, GeoUnit unit) : base(property)
80+
{
81+
this.lon = lon;
82+
this.lat = lat;
83+
this.radius = radius;
84+
this.unit = unit;
85+
}
86+
87+
internal override void SerializeRedisArgs(List<object> args)
88+
{
89+
args.Add("GEOFILTER".Literal());
90+
args.Add(Property);
91+
args.Add(lon);
92+
args.Add(lat);
93+
args.Add(radius);
94+
95+
switch (unit)
96+
{
97+
case GeoUnit.Feet: args.Add("ft".Literal()); break;
98+
case GeoUnit.Kilometers: args.Add("km".Literal()); break;
99+
case GeoUnit.Meters: args.Add("m".Literal()); break;
100+
case GeoUnit.Miles: args.Add("mi".Literal()); break;
101+
default: throw new InvalidOperationException($"Unknown unit: {unit}");
102+
}
103+
}
104+
}
105+
106+
private struct Paging
107+
{
108+
public int Offset { get; }
109+
public int Count { get; }
110+
111+
public Paging(int offset, int count)
112+
{
113+
Offset = offset;
114+
Count = count;
115+
}
116+
}
117+
118+
/// <summary>
119+
/// The query's filter list. We only support AND operation on all those filters
120+
/// </summary>
121+
List<Filter> _filters = new List<Filter>();
122+
123+
/// <summary>
124+
/// The textual part of the query
125+
/// </summary>
126+
public string QueryString { get; }
127+
128+
/// <summary>
129+
/// The sorting parameters
130+
/// </summary>
131+
Paging _paging = new Paging(0, 10);
132+
133+
/// <summary>
134+
/// Set the query to verbatim mode, disabling stemming and query expansion
135+
/// </summary>
136+
public bool Verbatim { get; set; }
137+
/// <summary>
138+
/// Set the query not to return the contents of documents, and rather just return the ids
139+
/// </summary>
140+
public bool NoContent { get; set; }
141+
/// <summary>
142+
/// Set the query not to filter for stopwords. In general this should not be used
143+
/// </summary>
144+
public bool NoStopwords { get; set; }
145+
/// <summary>
146+
/// Set the query to return a factored score for each results. This is useful to merge results from multiple queries.
147+
/// </summary>
148+
public bool WithScores { get; set; }
149+
/// <summary>
150+
/// Set the query to return object payloads, if any were given
151+
/// </summary>
152+
public bool WithPayloads { get; set; }
153+
154+
/// <summary>
155+
/// Set the query language, for stemming purposes; see http://redisearch.io for documentation on languages and stemming
156+
/// </summary>
157+
public string Language { get; set; }
158+
protected String[] _fields = null;
159+
/// <summary>
160+
/// Set the query payload to be evaluated by the scoring function
161+
/// </summary>
162+
public byte[] Payload { get; set; }
163+
164+
/// <summary>
165+
/// Create a new index
166+
/// </summary>
167+
public Query(String queryString)
168+
{
169+
QueryString = queryString;
170+
}
171+
172+
internal void SerializeRedisArgs(List<object> args)
173+
{
174+
args.Add(QueryString);
175+
176+
if (Verbatim)
177+
{
178+
args.Add("VERBATIM".Literal());
179+
}
180+
if (NoContent)
181+
{
182+
args.Add("NOCONTENT".Literal());
183+
}
184+
if (NoStopwords)
185+
{
186+
args.Add("NOSTOPWORDS".Literal());
187+
}
188+
if (WithScores)
189+
{
190+
args.Add("WITHSCORES".Literal());
191+
}
192+
if (WithPayloads)
193+
{
194+
args.Add("WITHPAYLOADS".Literal());
195+
}
196+
if (Language != null)
197+
{
198+
args.Add("LANGUAGE".Literal());
199+
args.Add(Language);
200+
}
201+
if (_fields != null && _fields.Length > 0)
202+
{
203+
args.Add("INFIELDS".Literal());
204+
args.Add(_fields.Length);
205+
args.AddRange(_fields);
206+
}
207+
208+
if (Payload != null)
209+
{
210+
args.Add("PAYLOAD".Literal());
211+
args.Add(Payload);
212+
}
213+
214+
if (_paging.Offset != 0 || _paging.Count != 10)
215+
{
216+
args.Add("LIMIT".Literal());
217+
args.Add(_paging.Offset);
218+
args.Add(_paging.Count);
219+
}
220+
221+
if (_filters != null && _filters.Count > 0)
222+
{
223+
foreach (var f in _filters)
224+
{
225+
f.SerializeRedisArgs(args);
226+
}
227+
}
228+
}
229+
230+
/// <summary>
231+
/// Limit the results to a certain offset and limit
232+
/// </summary>
233+
/// <param name="offset">the first result to show, zero based indexing</param>
234+
/// <param name="limit">how many results we want to show</param>
235+
/// <returns>the query itself, for builder-style syntax</returns>
236+
public Query Limit(int offset, int count)
237+
{
238+
_paging = new Paging(offset, count);
239+
return this;
240+
}
241+
242+
/// <summary>
243+
/// Add a filter to the query's filter list
244+
/// </summary>
245+
/// <param name="f">either a numeric or geo filter object</param>
246+
/// <returns>the query itself</returns>
247+
public Query AddFilter(Filter f)
248+
{
249+
_filters.Add(f);
250+
return this;
251+
}
252+
253+
/// <summary>
254+
/// Limit the query to results that are limited to a specific set of fields
255+
/// </summary>
256+
/// <param name="fields">a list of TEXT fields in the schemas</param>
257+
/// <returns>the query object itself</returns>
258+
public Query LimitFields(params string[] fields)
259+
{
260+
this._fields = fields;
261+
return this;
262+
}
263+
}
264+
}

0 commit comments

Comments
 (0)