Skip to content

Commit

Permalink
Add support for extended search commands (#151)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
shacharPash authored Jul 5, 2023
1 parent 16f7291 commit 9a2cd5d
Show file tree
Hide file tree
Showing 18 changed files with 1,149 additions and 124 deletions.
2 changes: 1 addition & 1 deletion Examples/AdvancedJsonExamples.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The ability to query within a JSON object unlocks further value to the underlyin
```
## Data Loading <a name="dataload"></a>
```c#
IJsonCommands json = db.JSON();
JsonCommands json = db.JSON();
json.Set("warehouse:1", "$", new {
city = "Boston",
location = "42.361145, -71.057083",
Expand Down
8 changes: 4 additions & 4 deletions Examples/AdvancedQueryOperations.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Aggregation and other more complex RediSearch queries
1. [Data Load](#vss_dataload)
2. [Index Creation](#vss_index)
3. [Search](#vss_search)
4. [Hybrid query Search](#vss_hybrid_query_search)
4. [Hybrid Query Search](#vss_hybrid_query_search)
4. [Advanced Search Queries](#adv_search)
1. [Data Set](#advs_dataset)
2. [Data Load](#advs_dataload)
Expand Down Expand Up @@ -66,7 +66,7 @@ db.HashSet("vec:4", new HashEntry[]
### Index Creation <a name="vss_index">
#### Command
```c#
ISearchCommands ft = db.FT();
SearchCommands ft = db.FT();
try {ft.DropIndex("vss_idx");} catch {};
Console.WriteLine(ft.Create("vss_idx", new FTCreateParams().On(IndexDataType.HASH).Prefix("vec:"),
new Schema()
Expand Down Expand Up @@ -193,7 +193,7 @@ vec:3 is not returned because it has tag B

### Data Load <a name="advs_dataload">
```c#
IJsonCommands json = db.JSON();
JsonCommands json = db.JSON();
json.Set("warehouse:1", "$", new {
city = "Boston",
location = "-71.057083, 42.361145",
Expand Down Expand Up @@ -253,7 +253,7 @@ json.Set("warehouse:2", "$", new {
### Index Creation <a name="advs_index">
#### Command
```c#
ISearchCommands ft = db.FT();
SearchCommands ft = db.FT();
try {ft.DropIndex("wh_idx");} catch {};
Console.WriteLine(ft.Create("wh_idx", new FTCreateParams()
.On(IndexDataType.JSON)
Expand Down
2 changes: 1 addition & 1 deletion Examples/BasicJsonExamples.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Document stores are a NoSQL database type that provide flexible schemas and acce
Insert a simple KVP as a JSON object.
#### Command
```c#
IJsonCommands json = db.JSON();
JsonCommands json = db.JSON();
Console.WriteLine(json.Set("ex1:1", "$", "\"val\""));
```
#### Result
Expand Down
4 changes: 2 additions & 2 deletions Examples/BasicQueryOperations.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ using NRedisStack.Search.Literals.Enums;
```
## Data Loading <a name="loading"></a>
```c#
IJsonCommands json = db.JSON();
JsonCommands json = db.JSON();
json.Set("product:15970", "$", new {
id = 15970,
gender = "Men",
Expand Down Expand Up @@ -100,7 +100,7 @@ json.Set("product:46885", "$", new {

#### Command
```c#
ISearchCommands ft = db.FT();
SearchCommands ft = db.FT();
try {ft.DropIndex("idx1");} catch {};
ft.Create("idx1", new FTCreateParams().On(IndexDataType.JSON)
.Prefix("product:"),
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ IDatabase db = redis.GetDatabase();
```
Now you can create a variable from any type of module in the following way:
```csharp
IBloomCommands bf = db.BF();
ICuckooCommands cf = db.CF();
ICmsCommands cms = db.CMS();
IGraphCommands graph = db.GRAPH();
ITopKCommands topk = db.TOPK();
ITdigestCommands tdigest = db.TDIGEST();
ISearchCommands ft = db.FT();
IJsonCommands json = db.JSON();
ITimeSeriesCommands ts = db.TS();
BloomCommands bf = db.BF();
CuckooCommands cf = db.CF();
CmsCommands cms = db.CMS();
GraphCommands graph = db.GRAPH();
TopKCommands topk = db.TOPK();
TdigestCommands tdigest = db.TDIGEST();
SearchCommands ft = db.FT();
JsonCommands json = db.JSON();
TimeSeriesCommands ts = db.TS();
```
Then, that variable will allow you to call all the commands of that module.

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

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

ISearchCommands ft = db.FT();
IJsonCommands json = db.JSON();
SearchCommands ft = db.FT();
JsonCommands json = db.JSON();
```

Create an index with fields and weights:
Expand Down
18 changes: 9 additions & 9 deletions src/NRedisStack/Pipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public Pipeline(IDatabase db)
public void Execute() => _batch.Execute();


public IBloomCommandsAsync Bf => new BloomCommandsAsync(_batch);
public ICmsCommandsAsync Cms => new CmsCommandsAsync(_batch);
public ICuckooCommandsAsync Cf => new CuckooCommandsAsync(_batch);
public IGraphCommandsAsync Graph => new GraphCommandsAsync(_batch);
public IJsonCommandsAsync Json => new JsonCommandsAsync(_batch);
public ISearchCommandsAsync Ft => new SearchCommandsAsync(_batch);
public ITdigestCommandsAsync Tdigest => new TdigestCommandsAsync(_batch);
public ITimeSeriesCommandsAsync Ts => new TimeSeriesCommandsAsync(_batch);
public ITopKCommandsAsync TopK => new TopKCommandsAsync(_batch);
public BloomCommandsAsync Bf => new BloomCommandsAsync(_batch);
public CmsCommandsAsync Cms => new CmsCommandsAsync(_batch);
public CuckooCommandsAsync Cf => new CuckooCommandsAsync(_batch);
public GraphCommandsAsync Graph => new GraphCommandsAsync(_batch);
public JsonCommandsAsync Json => new JsonCommandsAsync(_batch);
public SearchCommandsAsync Ft => new SearchCommandsAsync(_batch);
public TdigestCommandsAsync Tdigest => new TdigestCommandsAsync(_batch);
public TimeSeriesCommandsAsync Ts => new TimeSeriesCommandsAsync(_batch);
public TopKCommandsAsync TopK => new TopKCommandsAsync(_batch);

public IDatabaseAsync Db => _batch;
}
89 changes: 89 additions & 0 deletions src/NRedisStack/ResponseParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using NRedisStack.CountMinSketch.DataTypes;
using NRedisStack.TopK.DataTypes;
using NRedisStack.Tdigest.DataTypes;
using NRedisStack.Search;
using NRedisStack.Search.Aggregation;

namespace NRedisStack
{
Expand Down Expand Up @@ -614,6 +616,92 @@ public static IEnumerable<HashSet<string>> ToHashSets(this RedisResult result)
return sets;
}

public static Dictionary<string, Dictionary<string, double>> ToFtSpellCheckResult(this RedisResult result)
{
var rawTerms = (RedisResult[])result!;
var returnTerms = new Dictionary<string, Dictionary<string, double>>(rawTerms.Length);
foreach (var term in rawTerms)
{
var rawElements = (RedisResult[])term!;

string termValue = rawElements[1].ToString()!;

var list = (RedisResult[]) rawElements[2]!;
Dictionary<string, double> entries = new Dictionary<string, double>(list.Length);
foreach (var entry in list)
{
var entryElements = (RedisResult[])entry!;
string suggestion = entryElements[1].ToString()!;
double score = (double)entryElements[0];
entries.Add(suggestion, score);
}
returnTerms.Add(termValue, entries);
}

return returnTerms;
}

public static List<Tuple<string, double>> ToStringDoubleTupleList(this RedisResult result) // TODO: consider create class Suggestion instead of List<Tuple<string, double>>
{
var results = (RedisResult[])result!;
var list = new List<Tuple<string, double>>(results.Length / 2);
for (int i = 0; i < results.Length; i += 2)
{
var suggestion = results[i].ToString()!;
var score = (double)results[i + 1];
list.Add(new Tuple<string, double>(suggestion, score));
}
return list;
}

public static Dictionary<string, RedisResult> ToStringRedisResultDictionary(this RedisResult value)
{
var res = (RedisResult[])value!;
var dict = new Dictionary<string, RedisResult>();
foreach (var pair in res)
{
var arr = (RedisResult[])pair!;
dict.Add(arr[0].ToString(), arr[1]);
}
return dict;
}

public static Tuple<SearchResult, Dictionary<string, RedisResult>> ToProfileSearchResult(this RedisResult result, Query q)
{
var results = (RedisResult[])result!;

var searchResult = results[0].ToSearchResult(q);
var profile = results[1].ToStringRedisResultDictionary();
return new Tuple<SearchResult, Dictionary<string, RedisResult>>(searchResult, profile);
}

public static SearchResult ToSearchResult(this RedisResult result, Query q)
{
return new SearchResult((RedisResult[])result!, !q.NoContent, q.WithScores, q.WithPayloads/*, q.ExplainScore*/);
}

public static Tuple<AggregationResult, Dictionary<string, RedisResult>> ToProfileAggregateResult(this RedisResult result, AggregationRequest q)
{
var results = (RedisResult[])result!;
var aggregateResult = results[0].ToAggregationResult(q);
var profile = results[1].ToStringRedisResultDictionary();
return new Tuple<AggregationResult, Dictionary<string, RedisResult>>(aggregateResult, profile);
}

public static AggregationResult ToAggregationResult(this RedisResult result, AggregationRequest query)
{
if (query.IsWithCursor())
{
var results = (RedisResult[])result!;

return new AggregationResult(results[0], (long)results[1]);
}
else
{
return new AggregationResult(result);
}
}

public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult result)
{
var resArr = (RedisResult[])result!;
Expand All @@ -624,6 +712,7 @@ public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult r
}

return dicts;

}
}
}
96 changes: 96 additions & 0 deletions src/NRedisStack/Search/FTSpellCheckParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using NRedisStack.Search.Literals;
namespace NRedisStack.Search
{
public class FTSpellCheckParams
{
List<object> args = new List<object>();
private List<KeyValuePair<string, string>> terms = new List<KeyValuePair<string, string>>();
private int? distance = null;
private int? dialect = null;

public FTSpellCheckParams() { }

/// <summary>
/// Specifies an inclusion (INCLUDE) of a custom dictionary.
/// </summary>
public FTSpellCheckParams IncludeTerm(string dict)
{
return AddTerm(dict, SearchArgs.INCLUDE);
}

/// <summary>
/// Specifies an inclusion (EXCLUDE) of a custom dictionary.
/// </summary>
public FTSpellCheckParams ExcludeTerm(string dict)
{
return AddTerm(dict, SearchArgs.EXCLUDE);
}

/// <summary>
/// Specifies an inclusion (INCLUDE) or exclusion (EXCLUDE) of a custom dictionary.
/// </summary>
private FTSpellCheckParams AddTerm(string dict, string type)
{
terms.Add(new KeyValuePair<string, string>(dict, type));
return this;
}

/// <summary>
/// Maximum Levenshtein distance for spelling suggestions (default: 1, max: 4).
/// </summary>
public FTSpellCheckParams Distance(int distance)
{
this.distance = distance;
return this;
}

/// <summary>
/// Selects the dialect version under which to execute the query.
/// </summary>
public FTSpellCheckParams Dialect(int dialect)
{
this.dialect = dialect;
return this;
}

public List<object> GetArgs()
{
return args;
}

public void SerializeRedisArgs()
{
Distance();
Terms();
Dialect();
}

private void Dialect()
{
if (dialect != null)
{
args.Add(SearchArgs.DIALECT);
args.Add(dialect);
}
}

private void Terms()
{
foreach (var term in terms)
{
args.Add(SearchArgs.TERMS);
args.Add(term.Value);
args.Add(term.Key);
}
}

private void Distance()
{
if (distance != null)
{
args.Add(SearchArgs.DISTANCE);
args.Add(distance);
}
}
}
}
Loading

0 comments on commit 9a2cd5d

Please sign in to comment.