Skip to content

Commit 9a2cd5d

Browse files
authored
Add support for extended search commands (#151)
* start working on Adding Support for FT.SPELLCHECK * tests * Add tests * Add Bounds tests * Chagne to RedisServerException * fixes * add comands to interface * using SearchArgs * Commands implement * delete unused literals * TestAddAndGetSuggestion * Add SugGetWithScoresAsync * assync tests * Support FT.PROFILE * fix parser * fix command builder * test + fixes * add tests * add TestProfileCommandBuilder * space * Try make Tran&pipe instances not interface type * Add ft.create without FTCreateParams * tests * fix } missing * Delete 'I' from I<module>Commands intances in the examples * change to Uppercase * fixex after vlad's review
1 parent 16f7291 commit 9a2cd5d

18 files changed

+1149
-124
lines changed

Examples/AdvancedJsonExamples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ The ability to query within a JSON object unlocks further value to the underlyin
5050
```
5151
## Data Loading <a name="dataload"></a>
5252
```c#
53-
IJsonCommands json = db.JSON();
53+
JsonCommands json = db.JSON();
5454
json.Set("warehouse:1", "$", new {
5555
city = "Boston",
5656
location = "42.361145, -71.057083",

Examples/AdvancedQueryOperations.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Aggregation and other more complex RediSearch queries
77
1. [Data Load](#vss_dataload)
88
2. [Index Creation](#vss_index)
99
3. [Search](#vss_search)
10-
4. [Hybrid query Search](#vss_hybrid_query_search)
10+
4. [Hybrid Query Search](#vss_hybrid_query_search)
1111
4. [Advanced Search Queries](#adv_search)
1212
1. [Data Set](#advs_dataset)
1313
2. [Data Load](#advs_dataload)
@@ -66,7 +66,7 @@ db.HashSet("vec:4", new HashEntry[]
6666
### Index Creation <a name="vss_index">
6767
#### Command
6868
```c#
69-
ISearchCommands ft = db.FT();
69+
SearchCommands ft = db.FT();
7070
try {ft.DropIndex("vss_idx");} catch {};
7171
Console.WriteLine(ft.Create("vss_idx", new FTCreateParams().On(IndexDataType.HASH).Prefix("vec:"),
7272
new Schema()
@@ -193,7 +193,7 @@ vec:3 is not returned because it has tag B
193193

194194
### Data Load <a name="advs_dataload">
195195
```c#
196-
IJsonCommands json = db.JSON();
196+
JsonCommands json = db.JSON();
197197
json.Set("warehouse:1", "$", new {
198198
city = "Boston",
199199
location = "-71.057083, 42.361145",
@@ -253,7 +253,7 @@ json.Set("warehouse:2", "$", new {
253253
### Index Creation <a name="advs_index">
254254
#### Command
255255
```c#
256-
ISearchCommands ft = db.FT();
256+
SearchCommands ft = db.FT();
257257
try {ft.DropIndex("wh_idx");} catch {};
258258
Console.WriteLine(ft.Create("wh_idx", new FTCreateParams()
259259
.On(IndexDataType.JSON)

Examples/BasicJsonExamples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Document stores are a NoSQL database type that provide flexible schemas and acce
3838
Insert a simple KVP as a JSON object.
3939
#### Command
4040
```c#
41-
IJsonCommands json = db.JSON();
41+
JsonCommands json = db.JSON();
4242
Console.WriteLine(json.Set("ex1:1", "$", "\"val\""));
4343
```
4444
#### Result

Examples/BasicQueryOperations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ using NRedisStack.Search.Literals.Enums;
6565
```
6666
## Data Loading <a name="loading"></a>
6767
```c#
68-
IJsonCommands json = db.JSON();
68+
JsonCommands json = db.JSON();
6969
json.Set("product:15970", "$", new {
7070
id = 15970,
7171
gender = "Men",
@@ -100,7 +100,7 @@ json.Set("product:46885", "$", new {
100100

101101
#### Command
102102
```c#
103-
ISearchCommands ft = db.FT();
103+
SearchCommands ft = db.FT();
104104
try {ft.DropIndex("idx1");} catch {};
105105
ft.Create("idx1", new FTCreateParams().On(IndexDataType.JSON)
106106
.Prefix("product:"),

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ IDatabase db = redis.GetDatabase();
6060
```
6161
Now you can create a variable from any type of module in the following way:
6262
```csharp
63-
IBloomCommands bf = db.BF();
64-
ICuckooCommands cf = db.CF();
65-
ICmsCommands cms = db.CMS();
66-
IGraphCommands graph = db.GRAPH();
67-
ITopKCommands topk = db.TOPK();
68-
ITdigestCommands tdigest = db.TDIGEST();
69-
ISearchCommands ft = db.FT();
70-
IJsonCommands json = db.JSON();
71-
ITimeSeriesCommands ts = db.TS();
63+
BloomCommands bf = db.BF();
64+
CuckooCommands cf = db.CF();
65+
CmsCommands cms = db.CMS();
66+
GraphCommands graph = db.GRAPH();
67+
TopKCommands topk = db.TOPK();
68+
TdigestCommands tdigest = db.TDIGEST();
69+
SearchCommands ft = db.FT();
70+
JsonCommands json = db.JSON();
71+
TimeSeriesCommands ts = db.TS();
7272
```
7373
Then, that variable will allow you to call all the commands of that module.
7474

@@ -82,7 +82,7 @@ To store a json object in Redis:
8282
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
8383
IDatabase db = redis.GetDatabase();
8484

85-
IJsonCommands json = db.JSON();
85+
JsonCommands json = db.JSON();
8686
var key = "myKey";
8787
json.Set(key, "$", new { Age = 35, Name = "Alice" });
8888
```
@@ -99,8 +99,8 @@ using NRedisStack.Search.Literals.Enums;
9999
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
100100
IDatabase db = redis.GetDatabase();
101101

102-
ISearchCommands ft = db.FT();
103-
IJsonCommands json = db.JSON();
102+
SearchCommands ft = db.FT();
103+
JsonCommands json = db.JSON();
104104
```
105105

106106
Create an index with fields and weights:

src/NRedisStack/Pipeline.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ public Pipeline(IDatabase db)
1414
public void Execute() => _batch.Execute();
1515

1616

17-
public IBloomCommandsAsync Bf => new BloomCommandsAsync(_batch);
18-
public ICmsCommandsAsync Cms => new CmsCommandsAsync(_batch);
19-
public ICuckooCommandsAsync Cf => new CuckooCommandsAsync(_batch);
20-
public IGraphCommandsAsync Graph => new GraphCommandsAsync(_batch);
21-
public IJsonCommandsAsync Json => new JsonCommandsAsync(_batch);
22-
public ISearchCommandsAsync Ft => new SearchCommandsAsync(_batch);
23-
public ITdigestCommandsAsync Tdigest => new TdigestCommandsAsync(_batch);
24-
public ITimeSeriesCommandsAsync Ts => new TimeSeriesCommandsAsync(_batch);
25-
public ITopKCommandsAsync TopK => new TopKCommandsAsync(_batch);
17+
public BloomCommandsAsync Bf => new BloomCommandsAsync(_batch);
18+
public CmsCommandsAsync Cms => new CmsCommandsAsync(_batch);
19+
public CuckooCommandsAsync Cf => new CuckooCommandsAsync(_batch);
20+
public GraphCommandsAsync Graph => new GraphCommandsAsync(_batch);
21+
public JsonCommandsAsync Json => new JsonCommandsAsync(_batch);
22+
public SearchCommandsAsync Ft => new SearchCommandsAsync(_batch);
23+
public TdigestCommandsAsync Tdigest => new TdigestCommandsAsync(_batch);
24+
public TimeSeriesCommandsAsync Ts => new TimeSeriesCommandsAsync(_batch);
25+
public TopKCommandsAsync TopK => new TopKCommandsAsync(_batch);
2626

2727
public IDatabaseAsync Db => _batch;
2828
}

src/NRedisStack/ResponseParser.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using NRedisStack.CountMinSketch.DataTypes;
88
using NRedisStack.TopK.DataTypes;
99
using NRedisStack.Tdigest.DataTypes;
10+
using NRedisStack.Search;
11+
using NRedisStack.Search.Aggregation;
1012

1113
namespace NRedisStack
1214
{
@@ -614,6 +616,92 @@ public static IEnumerable<HashSet<string>> ToHashSets(this RedisResult result)
614616
return sets;
615617
}
616618

619+
public static Dictionary<string, Dictionary<string, double>> ToFtSpellCheckResult(this RedisResult result)
620+
{
621+
var rawTerms = (RedisResult[])result!;
622+
var returnTerms = new Dictionary<string, Dictionary<string, double>>(rawTerms.Length);
623+
foreach (var term in rawTerms)
624+
{
625+
var rawElements = (RedisResult[])term!;
626+
627+
string termValue = rawElements[1].ToString()!;
628+
629+
var list = (RedisResult[]) rawElements[2]!;
630+
Dictionary<string, double> entries = new Dictionary<string, double>(list.Length);
631+
foreach (var entry in list)
632+
{
633+
var entryElements = (RedisResult[])entry!;
634+
string suggestion = entryElements[1].ToString()!;
635+
double score = (double)entryElements[0];
636+
entries.Add(suggestion, score);
637+
}
638+
returnTerms.Add(termValue, entries);
639+
}
640+
641+
return returnTerms;
642+
}
643+
644+
public static List<Tuple<string, double>> ToStringDoubleTupleList(this RedisResult result) // TODO: consider create class Suggestion instead of List<Tuple<string, double>>
645+
{
646+
var results = (RedisResult[])result!;
647+
var list = new List<Tuple<string, double>>(results.Length / 2);
648+
for (int i = 0; i < results.Length; i += 2)
649+
{
650+
var suggestion = results[i].ToString()!;
651+
var score = (double)results[i + 1];
652+
list.Add(new Tuple<string, double>(suggestion, score));
653+
}
654+
return list;
655+
}
656+
657+
public static Dictionary<string, RedisResult> ToStringRedisResultDictionary(this RedisResult value)
658+
{
659+
var res = (RedisResult[])value!;
660+
var dict = new Dictionary<string, RedisResult>();
661+
foreach (var pair in res)
662+
{
663+
var arr = (RedisResult[])pair!;
664+
dict.Add(arr[0].ToString(), arr[1]);
665+
}
666+
return dict;
667+
}
668+
669+
public static Tuple<SearchResult, Dictionary<string, RedisResult>> ToProfileSearchResult(this RedisResult result, Query q)
670+
{
671+
var results = (RedisResult[])result!;
672+
673+
var searchResult = results[0].ToSearchResult(q);
674+
var profile = results[1].ToStringRedisResultDictionary();
675+
return new Tuple<SearchResult, Dictionary<string, RedisResult>>(searchResult, profile);
676+
}
677+
678+
public static SearchResult ToSearchResult(this RedisResult result, Query q)
679+
{
680+
return new SearchResult((RedisResult[])result!, !q.NoContent, q.WithScores, q.WithPayloads/*, q.ExplainScore*/);
681+
}
682+
683+
public static Tuple<AggregationResult, Dictionary<string, RedisResult>> ToProfileAggregateResult(this RedisResult result, AggregationRequest q)
684+
{
685+
var results = (RedisResult[])result!;
686+
var aggregateResult = results[0].ToAggregationResult(q);
687+
var profile = results[1].ToStringRedisResultDictionary();
688+
return new Tuple<AggregationResult, Dictionary<string, RedisResult>>(aggregateResult, profile);
689+
}
690+
691+
public static AggregationResult ToAggregationResult(this RedisResult result, AggregationRequest query)
692+
{
693+
if (query.IsWithCursor())
694+
{
695+
var results = (RedisResult[])result!;
696+
697+
return new AggregationResult(results[0], (long)results[1]);
698+
}
699+
else
700+
{
701+
return new AggregationResult(result);
702+
}
703+
}
704+
617705
public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult result)
618706
{
619707
var resArr = (RedisResult[])result!;
@@ -624,6 +712,7 @@ public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult r
624712
}
625713

626714
return dicts;
715+
627716
}
628717
}
629718
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using NRedisStack.Search.Literals;
2+
namespace NRedisStack.Search
3+
{
4+
public class FTSpellCheckParams
5+
{
6+
List<object> args = new List<object>();
7+
private List<KeyValuePair<string, string>> terms = new List<KeyValuePair<string, string>>();
8+
private int? distance = null;
9+
private int? dialect = null;
10+
11+
public FTSpellCheckParams() { }
12+
13+
/// <summary>
14+
/// Specifies an inclusion (INCLUDE) of a custom dictionary.
15+
/// </summary>
16+
public FTSpellCheckParams IncludeTerm(string dict)
17+
{
18+
return AddTerm(dict, SearchArgs.INCLUDE);
19+
}
20+
21+
/// <summary>
22+
/// Specifies an inclusion (EXCLUDE) of a custom dictionary.
23+
/// </summary>
24+
public FTSpellCheckParams ExcludeTerm(string dict)
25+
{
26+
return AddTerm(dict, SearchArgs.EXCLUDE);
27+
}
28+
29+
/// <summary>
30+
/// Specifies an inclusion (INCLUDE) or exclusion (EXCLUDE) of a custom dictionary.
31+
/// </summary>
32+
private FTSpellCheckParams AddTerm(string dict, string type)
33+
{
34+
terms.Add(new KeyValuePair<string, string>(dict, type));
35+
return this;
36+
}
37+
38+
/// <summary>
39+
/// Maximum Levenshtein distance for spelling suggestions (default: 1, max: 4).
40+
/// </summary>
41+
public FTSpellCheckParams Distance(int distance)
42+
{
43+
this.distance = distance;
44+
return this;
45+
}
46+
47+
/// <summary>
48+
/// Selects the dialect version under which to execute the query.
49+
/// </summary>
50+
public FTSpellCheckParams Dialect(int dialect)
51+
{
52+
this.dialect = dialect;
53+
return this;
54+
}
55+
56+
public List<object> GetArgs()
57+
{
58+
return args;
59+
}
60+
61+
public void SerializeRedisArgs()
62+
{
63+
Distance();
64+
Terms();
65+
Dialect();
66+
}
67+
68+
private void Dialect()
69+
{
70+
if (dialect != null)
71+
{
72+
args.Add(SearchArgs.DIALECT);
73+
args.Add(dialect);
74+
}
75+
}
76+
77+
private void Terms()
78+
{
79+
foreach (var term in terms)
80+
{
81+
args.Add(SearchArgs.TERMS);
82+
args.Add(term.Value);
83+
args.Add(term.Key);
84+
}
85+
}
86+
87+
private void Distance()
88+
{
89+
if (distance != null)
90+
{
91+
args.Add(SearchArgs.DISTANCE);
92+
args.Add(distance);
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)