diff --git a/src/NRedisStack/ModulPrefixes.cs b/src/NRedisStack/ModulePrefixes.cs similarity index 82% rename from src/NRedisStack/ModulPrefixes.cs rename to src/NRedisStack/ModulePrefixes.cs index 3af7cb49..e497f874 100644 --- a/src/NRedisStack/ModulPrefixes.cs +++ b/src/NRedisStack/ModulePrefixes.cs @@ -2,7 +2,7 @@ namespace NRedisStack.RedisStackCommands { - public static class ModulPrefixes + public static class ModulePrefixes { public static BloomCommands BF(this IDatabase db) => new BloomCommands(db); @@ -16,7 +16,7 @@ public static class ModulPrefixes public static TdigestCommands TDIGEST(this IDatabase db) => new TdigestCommands(db); - public static SearchCommands FT(this IDatabase db) => new SearchCommands(db); + public static SearchCommands FT(this IDatabase db, int? searchDialect = null) => new SearchCommands(db, searchDialect); public static JsonCommands JSON(this IDatabase db) => new JsonCommands(db); diff --git a/src/NRedisStack/Search/AggregationRequest.cs b/src/NRedisStack/Search/AggregationRequest.cs index 0128fe8d..e5854f99 100644 --- a/src/NRedisStack/Search/AggregationRequest.cs +++ b/src/NRedisStack/Search/AggregationRequest.cs @@ -7,96 +7,113 @@ public class AggregationRequest private List args = new List(); // Check if Readonly private bool isWithCursor = false; - public AggregationRequest(string query) + // Parameters: + + private bool? verbatim = null; + + // Load + private List fieldNames = new List(); // TODO: Check if the new list suposed to be here + private bool? loadAll = null; + + private long? timeout = null; + + // GroupBy: + private List groups = new List(); + + // SotrBy: + private List sortedFields = new List(); + private int? max = null; + + // Apply: + private List> apply = new List>(); + + // Limit: + private int? offset = null; + private int? num = null; + + private string? filter = null; + + // WithCursor: + private int? count = null; + private long? maxIdle = null; + + // Params: + private Dictionary nameValue = new Dictionary(); + public int? dialect {get; private set;} = null; + + public AggregationRequest(string query, int? defaultDialect = null) { + this.dialect = defaultDialect; args.Add(query); } public AggregationRequest() : this("*") { } - // public AggregationRequest load(params string[] fields) - // { - // return load(FieldName.Convert(fields)); - // } - - public AggregationRequest Load(params FieldName[] fields) + public AggregationRequest Verbatim(bool verbatim = true) { - args.Add("LOAD"); - int loadCountIndex = args.Count(); - args.Add(null); - int loadCount = 0; - foreach (FieldName fn in fields) - { - loadCount += fn.AddCommandArguments(args); - } - args.Insert(loadCountIndex, loadCount.ToString()); + this.verbatim = true; return this; } - public AggregationRequest LoadAll() + private void Verbatim() { - args.Add("LOAD"); - args.Add("*"); - return this; + if(verbatim == true) + args.Add("VERBATIM"); } - public AggregationRequest Limit(int offset, int count) + public AggregationRequest Load(params FieldName[] fields) { - Limit limit = new Limit(offset, count); - limit.SerializeRedisArgs(args); + this.fieldNames.AddRange(fields); return this; } - public AggregationRequest Limit(int count) => Limit(0, count); - - public AggregationRequest SortBy(string property) => SortBy(SortedField.Asc(property)); + public AggregationRequest LoadAll() + { + loadAll = true; + return this; + } - public AggregationRequest SortBy(params SortedField[] Fields) + private void Load() { - args.Add("SORTBY"); - args.Add(Fields.Length * 2); - foreach (SortedField field in Fields) + if (loadAll == true) { - args.Add(field.FieldName); - args.Add(field.Order); + args.Add("LOAD"); + args.Add("*"); + return; } + else if (fieldNames.Count > 0) + { + args.Add("LOAD"); + int loadCountIndex = args.Count; + //args.Add(null); + int loadCount = 0; + foreach (FieldName fn in fieldNames) + { + loadCount += fn.AddCommandArguments(args); + } - return this; + args.Insert(loadCountIndex, loadCount); + // args[loadCountIndex] = loadCount.ToString(); + } } - public AggregationRequest SortBy(int max, params SortedField[] Fields) + public AggregationRequest Timeout(long timeout) { - SortBy(Fields); - if (max > 0) - { - args.Add("MAX"); - args.Add(max); - } + this.timeout = timeout; return this; } - // public AggregationRequest SortByAsc(string field) - // { - // return SortBy(SortedField.Asc(field)); - // } - - // public AggregationRequest SortByDesc(string field) - // { - // return SortBy(SortedField.Desc(field)); - // } - - public AggregationRequest Apply(string projection, string alias) + private void Timeout() { - args.Add("APPLY"); - args.Add(projection); - args.Add("AS"); - args.Add(alias); - return this; + if (timeout != null) + { + args.Add("TIMEOUT"); + args.Add(timeout); + } } public AggregationRequest GroupBy(IList fields, IList reducers) { - // string[] fieldsArr = new string[fields.size()]; Group g = new Group(fields); foreach (Reducer r in reducers) { @@ -113,54 +130,153 @@ public AggregationRequest GroupBy(string field, params Reducer[] reducers) public AggregationRequest GroupBy(Group group) { - args.Add("GROUPBY"); - group.SerializeRedisArgs(args); + this.groups.Add(group); return this; } - public AggregationRequest Filter(string expression) + private void GroupBy() { - args.Add(SearchArgs.FILTER); - args.Add(expression); + if (groups.Count > 0) + { + args.Add("GROUPBY"); + foreach (Group group in groups) + { + group.SerializeRedisArgs(args); + } + } + } + + public AggregationRequest SortBy(string property) => SortBy(SortedField.Asc(property)); + + public AggregationRequest SortBy(params SortedField[] fields) + { + this.sortedFields.AddRange(fields); return this; } - public AggregationRequest Cursor(int count, long maxIdle) + private void SortBy() { - isWithCursor = true; - if (count > 0) + if (sortedFields.Count > 0) { - args.Add("WITHCURSOR"); - args.Add("COUNT"); - args.Add(count); - if (maxIdle < long.MaxValue && maxIdle >= 0) + args.Add("SORTBY"); + args.Add(sortedFields.Count * 2); + foreach (SortedField field in sortedFields) { - args.Add("MAXIDLE"); - args.Add(maxIdle); + args.Add(field.FieldName); + args.Add(field.Order.ToString()); + } + + if (max > 0) + { + args.Add("MAX"); + args.Add(max); + } + } + } + + public AggregationRequest SortBy(int max, params SortedField[] fields) + { + this.max = max; + SortBy(fields); + return this; + } + + public AggregationRequest Apply(string projection, string alias) + { + apply.Add(new Tuple(projection, alias)); + return this; + } + + private void Apply() + { + if (apply.Count > 0) + { + foreach (Tuple tuple in apply) + { + args.Add("APPLY"); + args.Add(tuple.Item1); + args.Add("AS"); + args.Add(tuple.Item2); } } + } + + public AggregationRequest Limit(int count) => Limit(0, count); + + public AggregationRequest Limit(int offset, int count) + { + this.offset = offset; + this.num = count; + return this; } - public AggregationRequest Verbatim() + private void Limit() { - args.Add("VERBATIM"); + if (offset != null && num != null) + { + new Limit(offset.Value, num.Value).SerializeRedisArgs(args); + } + } + + public AggregationRequest Filter(string filter) + { + this.filter = filter; return this; } - public AggregationRequest Timeout(long timeout) + private void Filter() { - if (timeout >= 0) + if (filter != null) { - args.Add("TIMEOUT"); - args.Add(timeout); + args.Add(SearchArgs.FILTER); + args.Add(filter!); } + + } + + public AggregationRequest Cursor(int? count = null, long? maxIdle = null) + { + isWithCursor = true; + if (count != null) + this.count = count; + if (maxIdle != null) + this.maxIdle = maxIdle; return this; } + private void Cursor() + { + if (isWithCursor) + { + args.Add("WITHCURSOR"); + + if (count != null) + { + args.Add("COUNT"); + args.Add(count); + } + + if (maxIdle != null && maxIdle < long.MaxValue && maxIdle >= 0) + { + args.Add("MAXIDLE"); + args.Add(maxIdle); + } + } + } + public AggregationRequest Params(Dictionary nameValue) { - if (nameValue.Count >= 1) + foreach (var entry in nameValue) + { + this.nameValue.Add(entry.Key, entry.Value); + } + return this; + } + + private void Params() + { + if (nameValue.Count > 0) { args.Add("PARAMS"); args.Add(nameValue.Count * 2); @@ -170,29 +286,42 @@ public AggregationRequest Params(Dictionary nameValue) args.Add(entry.Value); } } - - return this; } public AggregationRequest Dialect(int dialect) { - args.Add("DIALECT"); - args.Add(dialect); + this.dialect = dialect; return this; } + private void Dialect() + { + if (dialect != null) + { + args.Add("DIALECT"); + args.Add(dialect); + } + } + public List GetArgs() { return args; } - // public void SerializeRedisArgs(List redisArgs) - // { - // foreach (var s in GetArgs()) - // { - // redisArgs.Add(s); - // } - // } + public void SerializeRedisArgs() + { + Verbatim(); + Load(); + Timeout(); + Apply(); + GroupBy(); + SortBy(); + Limit(); + Filter(); + Cursor(); + Params(); + Dialect(); + } // public string getArgsstring() // { diff --git a/src/NRedisStack/Search/Query.cs b/src/NRedisStack/Search/Query.cs index cf7d5ae1..0fa42f6c 100644 --- a/src/NRedisStack/Search/Query.cs +++ b/src/NRedisStack/Search/Query.cs @@ -191,8 +191,8 @@ public HighlightTags(string open, string close) public string Scorer { get; set; } // public bool ExplainScore { get; set; } // TODO: Check if this is needed because Jedis doesn't have it - private Dictionary _params = null; - private int _dialect = 0; + private Dictionary _params = new Dictionary(); + public int? dialect { get; private set;} = null; private int _slop = -1; private long _timeout = -1; private bool _inOrder = false; @@ -374,10 +374,10 @@ internal void SerializeRedisArgs(List args) } } - if (_dialect != 0) + if (dialect >= 1) { args.Add("DIALECT"); - args.Add(_dialect); + args.Add(dialect); } if (_slop >= 0) @@ -621,13 +621,18 @@ public Query SetSortBy(string field, bool? ascending = null) /// /// can be String, long or float /// The query object itself - public Query AddParam(String name, Object value) + public Query AddParam(string name, object value) { - if (_params == null) + _params.Add(name, value); + return this; + } + + public Query Params(Dictionary nameValue) + { + foreach (var entry in nameValue) { - _params = new Dictionary(); + _params.Add(entry.Key, entry.Value); } - _params.Add(name, value); return this; } @@ -638,7 +643,7 @@ public Query AddParam(String name, Object value) /// the query object itself public Query Dialect(int dialect) { - _dialect = dialect; + this.dialect = dialect; return this; } diff --git a/src/NRedisStack/Search/SearchCommandBuilder.cs b/src/NRedisStack/Search/SearchCommandBuilder.cs index 1331d733..7899c24e 100644 --- a/src/NRedisStack/Search/SearchCommandBuilder.cs +++ b/src/NRedisStack/Search/SearchCommandBuilder.cs @@ -15,10 +15,8 @@ public static SerializedCommand _List() public static SerializedCommand Aggregate(string index, AggregationRequest query) { List args = new List { index }; - foreach (var arg in query.GetArgs()) - { - if(arg != null) args.Add(arg.ToString()!); - } + query.SerializeRedisArgs(); + args.AddRange(query.GetArgs()); return new SerializedCommand(FT.AGGREGATE, args); } diff --git a/src/NRedisStack/Search/SearchCommands.cs b/src/NRedisStack/Search/SearchCommands.cs index 0c88fe87..c6ca622e 100644 --- a/src/NRedisStack/Search/SearchCommands.cs +++ b/src/NRedisStack/Search/SearchCommands.cs @@ -7,9 +7,20 @@ namespace NRedisStack public class SearchCommands : SearchCommandsAsync, ISearchCommands { IDatabase _db; - public SearchCommands(IDatabase db) : base(db) + public SearchCommands(IDatabase db, int? defaultDialect) : base(db) { _db = db; + SetDefaultDialect(defaultDialect); + this.defaultDialect = defaultDialect; + } + + public void SetDefaultDialect(int? defaultDialect) + { + if(defaultDialect == 0) + { + throw new System.ArgumentOutOfRangeException("DIALECT=0 cannot be set."); + } + this.defaultDialect = defaultDialect; } /// @@ -21,6 +32,11 @@ public RedisResult[] _List() /// public AggregationResult Aggregate(string index, AggregationRequest query) { + if(query.dialect == null && defaultDialect != null) + { + query.Dialect((int)defaultDialect); + } + var result = _db.Execute(SearchCommandBuilder.Aggregate(index, query)); if (query.IsWithCursor()) { @@ -116,12 +132,20 @@ public bool DropIndex(string indexName, bool dd = false) /// public string Explain(string indexName, Query q) { + if (q.dialect == null && defaultDialect != null) + { + q.Dialect((int)defaultDialect); + } return _db.Execute(SearchCommandBuilder.Explain(indexName, q)).ToString(); } /// public RedisResult[] ExplainCli(string indexName, Query q) { + if (q.dialect == null && defaultDialect != null) + { + q.Dialect((int)defaultDialect); + } return _db.Execute(SearchCommandBuilder.ExplainCli(indexName, q)).ToArray(); } @@ -134,6 +158,10 @@ public InfoResult Info(RedisValue index) => /// public SearchResult Search(string indexName, Query q) { + if (q.dialect == null && defaultDialect != null) + { + q.Dialect((int)defaultDialect); + } var resp = _db.Execute(SearchCommandBuilder.Search(indexName, q)).ToArray(); return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads/*, q.ExplainScore*/); } diff --git a/src/NRedisStack/Search/SearchCommandsAsync.cs b/src/NRedisStack/Search/SearchCommandsAsync.cs index 72ebbf0b..90c1f1d4 100644 --- a/src/NRedisStack/Search/SearchCommandsAsync.cs +++ b/src/NRedisStack/Search/SearchCommandsAsync.cs @@ -7,6 +7,8 @@ namespace NRedisStack public class SearchCommandsAsync : ISearchCommandsAsync { IDatabaseAsync _db; + protected int? defaultDialect; + public SearchCommandsAsync(IDatabaseAsync db) { _db = db; @@ -21,6 +23,10 @@ public async Task _ListAsync() /// public async Task AggregateAsync(string index, AggregationRequest query) { + if (query.dialect == null && defaultDialect != null) + { + query.Dialect((int)defaultDialect); + } var result = await _db.ExecuteAsync(SearchCommandBuilder.Aggregate(index, query)); if (query.IsWithCursor()) @@ -117,12 +123,22 @@ public async Task DropIndexAsync(string indexName, bool dd = false) /// public async Task ExplainAsync(string indexName, Query q) { + if (q.dialect == null && defaultDialect != null) + { + q.Dialect((int)defaultDialect); + } + return (await _db.ExecuteAsync(SearchCommandBuilder.Explain(indexName, q))).ToString(); } /// public async Task ExplainCliAsync(string indexName, Query q) { + if (q.dialect == null && defaultDialect != null) + { + q.Dialect((int)defaultDialect); + } + return (await _db.ExecuteAsync(SearchCommandBuilder.ExplainCli(indexName, q))).ToArray(); } @@ -135,6 +151,11 @@ public async Task InfoAsync(RedisValue index) => /// public async Task SearchAsync(string indexName, Query q) { + if (q.dialect == null && defaultDialect != null) + { + q.Dialect((int)defaultDialect); + } + var resp = (await _db.ExecuteAsync(SearchCommandBuilder.Search(indexName, q))).ToArray(); return new SearchResult(resp, !q.NoContent, q.WithScores, q.WithPayloads/*, q.ExplainScore*/); } diff --git a/tests/NRedisStack.Tests/Search/SearchTests.cs b/tests/NRedisStack.Tests/Search/SearchTests.cs index 61fd37a1..6c49e877 100644 --- a/tests/NRedisStack.Tests/Search/SearchTests.cs +++ b/tests/NRedisStack.Tests/Search/SearchTests.cs @@ -308,6 +308,7 @@ public void TestAggregationRequestParamsDialect() Dictionary parameters = new Dictionary(); parameters.Add("name", "abc"); + parameters.Add("count", "10"); AggregationRequest r = new AggregationRequest("$name") .GroupBy("@name", Reducers.Sum("@count").As("sum")) @@ -345,6 +346,75 @@ public async Task TestAggregationRequestParamsDialectAsync() .Params(parameters) .Dialect(2); // From documentation - To use PARAMS, DIALECT must be set to 2 + // Add more parameters using params (more than 1 is also possible): + parameters.Clear(); + parameters.Add("count", "10"); + r.Params(parameters); + + AggregationResult res = await ft.AggregateAsync(index, r); + Assert.Equal(1, res.TotalResults); + + Row r1 = res.GetRow(0); + Assert.NotNull(r1); + Assert.Equal("abc", r1.GetString("name")); + Assert.Equal(10, r1.GetLong("sum")); + } + + [Fact] + public void TestAggregationRequestParamsWithDefaultDialect() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + Schema sc = new Schema(); + sc.AddTextField("name", 1.0, sortable: true); + sc.AddNumericField("count", sortable: true); + ft.Create(index, FTCreateParams.CreateParams(), sc); + AddDocument(db, new Document("data1").Set("name", "abc").Set("count", 10)); + AddDocument(db, new Document("data2").Set("name", "def").Set("count", 5)); + AddDocument(db, new Document("data3").Set("name", "def").Set("count", 25)); + + Dictionary parameters = new Dictionary(); + parameters.Add("name", "abc"); + parameters.Add("count", "10"); + + AggregationRequest r = new AggregationRequest("$name") + .GroupBy("@name", Reducers.Sum("@count").As("sum")) + .Params(parameters); // From documentation - To use PARAMS, DIALECT must be set to 2 + // which is the default as we set in the constructor (FT(2)) + + AggregationResult res = ft.Aggregate(index, r); + Assert.Equal(1, res.TotalResults); + + Row r1 = res.GetRow(0); + Assert.NotNull(r1); + Assert.Equal("abc", r1.GetString("name")); + Assert.Equal(10, r1.GetLong("sum")); + } + + [Fact] + public async Task TestAggregationRequestParamsWithDefaultDialectAsync() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + Schema sc = new Schema(); + sc.AddTextField("name", 1.0, sortable: true); + sc.AddNumericField("count", sortable: true); + ft.Create(index, FTCreateParams.CreateParams(), sc); + AddDocument(db, new Document("data1").Set("name", "abc").Set("count", 10)); + AddDocument(db, new Document("data2").Set("name", "def").Set("count", 5)); + AddDocument(db, new Document("data3").Set("name", "def").Set("count", 25)); + + Dictionary parameters = new Dictionary(); + parameters.Add("name", "abc"); + parameters.Add("count", "10"); + + AggregationRequest r = new AggregationRequest("$name") + .GroupBy("@name", Reducers.Sum("@count").As("sum")) + .Params(parameters); // From documentation - To use PARAMS, DIALECT must be set to 2 + // which is the default as we set in the constructor (FT(2)) + AggregationResult res = await ft.AggregateAsync(index, r); Assert.Equal(1, res.TotalResults); @@ -354,6 +424,15 @@ public async Task TestAggregationRequestParamsDialectAsync() Assert.Equal(10, r1.GetLong("sum")); } + [Fact] + public void TestDefaultDialectError() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + // test error on invalid dialect: + Assert.Throws(() => db.FT(0)); + } + [Fact] public void TestAlias() { @@ -1250,7 +1329,7 @@ public void TestExplain() .AddTextField("f3", 1.0); ft.Create(index, FTCreateParams.CreateParams(), sc); - String res = ft.Explain(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); + string res = ft.Explain(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); Assert.NotNull(res); Assert.False(res.Length == 0); } @@ -1267,6 +1346,74 @@ public async Task TestExplainAsync() .AddTextField("f3", 1.0); ft.Create(index, FTCreateParams.CreateParams(), sc); + string res = await ft.ExplainAsync(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); + Assert.NotNull(res); + Assert.False(res.Length == 0); + } + + [Fact] + public void TestExplainCli() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + Schema sc = new Schema() + .AddTextField("f1", 1.0) + .AddTextField("f2", 1.0) + .AddTextField("f3", 1.0); + ft.Create(index, FTCreateParams.CreateParams(), sc); + + var res = ft.ExplainCli(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); + Assert.NotNull(res); + Assert.False(res.Length == 0); + } + + [Fact] + public async Task TestExplainCliAsync() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + Schema sc = new Schema() + .AddTextField("f1", 1.0) + .AddTextField("f2", 1.0) + .AddTextField("f3", 1.0); + ft.Create(index, FTCreateParams.CreateParams(), sc); + + var res = await ft.ExplainCliAsync(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); + Assert.NotNull(res); + Assert.False(res.Length == 0); + } + + [Fact] + public void TestExplainWithDefaultDialect() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(1); + Schema sc = new Schema() + .AddTextField("f1", 1.0) + .AddTextField("f2", 1.0) + .AddTextField("f3", 1.0); + ft.Create(index, FTCreateParams.CreateParams(), sc); + + String res = ft.Explain(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); + Assert.NotNull(res); + Assert.False(res.Length == 0); + } + + [Fact] + public async Task TestExplainWithDefaultDialectAsync() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(1); + Schema sc = new Schema() + .AddTextField("f1", 1.0) + .AddTextField("f2", 1.0) + .AddTextField("f3", 1.0); + ft.Create(index, FTCreateParams.CreateParams(), sc); + String res = await ft.ExplainAsync(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); Assert.NotNull(res); Assert.False(res.Length == 0); @@ -1739,6 +1886,7 @@ public void TestQueryCommandBuilderReturnField() "1", "txt"}; + Assert.Equal(expectedArgs.Count(), buildCommand.Args.Count()); for (int i = 0; i < buildCommand.Args.Count(); i++) { Assert.Equal(expectedArgs[i].ToString(), buildCommand.Args[i].ToString()); @@ -1842,11 +1990,11 @@ public void TestLimit() AddDocument(db, doc1); AddDocument(db, doc2); - var req = new AggregationRequest("*").SortBy("@t1").Limit(1, 1); + var req = new AggregationRequest("*").SortBy("@t1").Limit(1); var res = ft.Aggregate("idx", req); Assert.Equal( res.GetResults().Count, 1); - Assert.Equal( res.GetResults()[0]["t1"].ToString(), "b"); + Assert.Equal( res.GetResults()[0]["t1"].ToString(), "a"); } [Fact] @@ -2010,7 +2158,100 @@ public async Task TestVectorFieldJson_Issue102Async() }); Assert.True(await ft.CreateAsync("my_index", new FTCreateParams().On(IndexDataType.JSON), schema)); + } + + [Fact] + public void TestQueryAddParam_DefaultDialect() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + + var sc = new Schema().AddNumericField("numval"); + Assert.True(ft.Create("idx", new FTCreateParams(), sc)); + + db.HashSet("1", "numval", 1); + db.HashSet("2", "numval", 2); + db.HashSet("3", "numval", 3); + + Query query = new Query("@numval:[$min $max]").AddParam("min", 1).AddParam("max", 2); + var res = ft.Search("idx", query); + Assert.Equal(2, res.TotalResults); + } + + [Fact] + public async Task TestQueryAddParam_DefaultDialectAsync() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + + var sc = new Schema().AddNumericField("numval"); + Assert.True(await ft.CreateAsync("idx", new FTCreateParams(), sc)); + + db.HashSet("1", "numval", 1); + db.HashSet("2", "numval", 2); + db.HashSet("3", "numval", 3); + + Query query = new Query("@numval:[$min $max]").AddParam("min", 1).AddParam("max", 2); + var res = await ft.SearchAsync("idx", query); + Assert.Equal(2, res.TotalResults); + } + + [Fact] + public void TestQueryParamsWithParams_DefaultDialect() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + var sc = new Schema().AddNumericField("numval"); + Assert.True(ft.Create("idx", new FTCreateParams(), sc)); + + db.HashSet("1", "numval", 1); + db.HashSet("2", "numval", 2); + db.HashSet("3", "numval", 3); + + Query query = new Query("@numval:[$min $max]").AddParam("min", 1).AddParam("max", 2); + var res = ft.Search("idx", query); + Assert.Equal(2, res.TotalResults); + + var paramValue = new Dictionary() + { + ["min"] = 1, + ["max"] = 2 + }; + query = new Query("@numval:[$min $max]"); + res = ft.Search("idx", query.Params(paramValue)); + Assert.Equal(2, res.TotalResults); + } + + [Fact] + public async Task TestQueryParamsWithParams_DefaultDialectAsync() + { + IDatabase db = redisFixture.Redis.GetDatabase(); + db.Execute("FLUSHALL"); + var ft = db.FT(2); + + var sc = new Schema().AddNumericField("numval"); + Assert.True(await ft.CreateAsync("idx", new FTCreateParams(), sc)); + + db.HashSet("1", "numval", 1); + db.HashSet("2", "numval", 2); + db.HashSet("3", "numval", 3); + + Query query = new Query("@numval:[$min $max]").AddParam("min", 1).AddParam("max", 2); + var res = await ft.SearchAsync("idx", query); + Assert.Equal(2, res.TotalResults); + + var paramValue = new Dictionary() + { + ["min"] = 1, + ["max"] = 2 + }; + query = new Query("@numval:[$min $max]"); + res = await ft.SearchAsync("idx", query.Params(paramValue)); + Assert.Equal(2, res.TotalResults); } [Fact]