diff --git a/src/NRedisStack/Search/AggregationRequest.cs b/src/NRedisStack/Search/AggregationRequest.cs index 29eee63c..705b1b66 100644 --- a/src/NRedisStack/Search/AggregationRequest.cs +++ b/src/NRedisStack/Search/AggregationRequest.cs @@ -7,38 +7,6 @@ public class AggregationRequest private List args = new List(); // Check if Readonly private bool isWithCursor = false; - // 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) @@ -49,67 +17,48 @@ public AggregationRequest(string query, int? defaultDialect = null) public AggregationRequest() : this("*") { } - public AggregationRequest Verbatim(bool verbatim = true) + public AggregationRequest Verbatim() { - this.verbatim = true; + args.Add(SearchArgs.VERBATIM); return this; } - private void Verbatim() - { - if (verbatim == true) - args.Add("VERBATIM"); - } - public AggregationRequest Load(params FieldName[] fields) { - this.fieldNames.AddRange(fields); - return this; - } - - public AggregationRequest LoadAll() - { - loadAll = true; - return this; - } - - private void Load() - { - if (loadAll == true) + if (fields.Length > 0) { - args.Add("LOAD"); - args.Add("*"); - return; - } - else if (fieldNames.Count > 0) - { - args.Add("LOAD"); + args.Add(SearchArgs.LOAD); int loadCountIndex = args.Count; - //args.Add(null); int loadCount = 0; - foreach (FieldName fn in fieldNames) + foreach (FieldName fn in fields) { loadCount += fn.AddCommandArguments(args); } args.Insert(loadCountIndex, loadCount); - // args[loadCountIndex] = loadCount.ToString(); } + return this; + } + + public AggregationRequest LoadAll() + { + args.Add(SearchArgs.LOAD); + args.Add("*"); + return this; } public AggregationRequest Timeout(long timeout) { - this.timeout = timeout; + args.Add(SearchArgs.TIMEOUT); + args.Add(timeout); return this; } - private void Timeout() + + + public AggregationRequest GroupBy(string field, params Reducer[] reducers) { - if (timeout != null) - { - args.Add("TIMEOUT"); - args.Add(timeout); - } + return GroupBy(new string[] { field }, reducers); } public AggregationRequest GroupBy(IList fields, IList reducers) @@ -123,162 +72,85 @@ public AggregationRequest GroupBy(IList fields, IList reducers) return this; } - public AggregationRequest GroupBy(string field, params Reducer[] reducers) - { - return GroupBy(new string[] { field }, reducers); - } - public AggregationRequest GroupBy(Group group) { - this.groups.Add(group); + args.Add(SearchArgs.GROUPBY); + group.SerializeRedisArgs(args); return this; } - private void GroupBy() - { - 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 SortBy(params SortedField[] fields) => SortBy(-1, fields); - private void SortBy() + public AggregationRequest SortBy(int max, params SortedField[] fields) { - if (sortedFields.Count > 0) + args.Add(SearchArgs.SORTBY); + args.Add(fields.Length * 2); + + foreach (SortedField field in fields) { - args.Add("SORTBY"); - args.Add(sortedFields.Count * 2); - foreach (SortedField field in sortedFields) - { - args.Add(field.FieldName); - args.Add(field.Order.ToString()); - } + args.Add(field.FieldName); + args.Add(field.Order.ToString()); + } - if (max > 0) - { - args.Add("MAX"); - args.Add(max); - } + if (max > 0) + { + args.Add(SearchArgs.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)); + args.Add(SearchArgs.APPLY); + args.Add(projection); + args.Add(SearchArgs.AS); + args.Add(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; - + new Limit(offset, count).SerializeRedisArgs(args); return this; } - private void Limit() - { - if (offset != null && num != null) - { - new Limit(offset.Value, num.Value).SerializeRedisArgs(args); - } - } - public AggregationRequest Filter(string filter) { - this.filter = filter; + args.Add(SearchArgs.FILTER); + args.Add(filter!); return this; } - private void Filter() - { - if (filter != null) - { - 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; - } + args.Add(SearchArgs.WITHCURSOR); - private void Cursor() - { - if (isWithCursor) + if (count != null) { - 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); - } + args.Add(SearchArgs.COUNT); + args.Add(count); } - } - public AggregationRequest Params(Dictionary nameValue) - { - foreach (var entry in nameValue) + if (maxIdle != null && maxIdle < long.MaxValue && maxIdle >= 0) { - this.nameValue.Add(entry.Key, entry.Value); + args.Add(SearchArgs.MAXIDLE); + args.Add(maxIdle); } return this; } - private void Params() + public AggregationRequest Params(Dictionary nameValue) { if (nameValue.Count > 0) { - args.Add("PARAMS"); + args.Add(SearchArgs.PARAMS); args.Add(nameValue.Count * 2); foreach (var entry in nameValue) { @@ -286,6 +158,7 @@ private void Params() args.Add(entry.Value); } } + return this; } public AggregationRequest Dialect(int dialect) @@ -298,7 +171,7 @@ private void Dialect() { if (dialect != null) { - args.Add("DIALECT"); + args.Add(SearchArgs.DIALECT); args.Add(dialect); } } @@ -310,29 +183,9 @@ public List GetArgs() public void SerializeRedisArgs() { - Verbatim(); - Load(); - Timeout(); - Apply(); - GroupBy(); - SortBy(); - Limit(); - Filter(); - Cursor(); - Params(); Dialect(); } - // public string getArgsstring() - // { - // StringBuilder sj = new StringBuilder(" "); - // foreach (var s in GetArgs()) - // { - // sj.Add(s.ToString()); - // } - // return sj.tostring(); - // } - public bool IsWithCursor() { return isWithCursor; diff --git a/src/NRedisStack/Search/Literals/CommandArgs.cs b/src/NRedisStack/Search/Literals/CommandArgs.cs index fb957db7..7910bc53 100644 --- a/src/NRedisStack/Search/Literals/CommandArgs.cs +++ b/src/NRedisStack/Search/Literals/CommandArgs.cs @@ -2,60 +2,74 @@ namespace NRedisStack.Search.Literals { internal class SearchArgs { - public const string ON_HASH = "ON HASH"; - public const string JSON = "JSON"; - public const string PREFIX = "PREFIX"; + public const string AGGREGATE = "AGGREGATE"; + public const string APPLY = "APPLY"; + public const string AS = "AS"; + public const string ASC = "ASC"; + public const string CASESENSITIVE = "CASESENSITIVE"; + public const string COUNT = "COUNT"; + public const string DESC = "DESC"; + public const string DIALECT = "DIALECT"; + public const string DISTANCE = "DISTANCE"; + public const string EXCLUDE = "EXCLUDE"; + public const string EXPANDER = "EXPANDER"; + public const string FIELDS = "FIELDS"; public const string FILTER = "FILTER"; + public const string FRAGS = "FRAGS"; + public const string FUZZY = "FUZZY"; + public const string GROUPBY = "GROUPBY"; + public const string HIGHLIGHT = "HIGHLIGHT"; + public const string INCLUDE = "INCLUDE"; + public const string INCR = "INCR"; + public const string INFIELDS = "INFIELDS"; + public const string INKEYS = "INKEYS"; + public const string INORDER = "INORDER"; + public const string JSON = "JSON"; public const string LANGUAGE = "LANGUAGE"; public const string LANGUAGE_FIELD = "LANGUAGE_FIELD"; - public const string SCORE = "SCORE"; - public const string SCORE_FIELD = "SCORE_FIELD"; - public const string PAYLOAD_FIELD = "PAYLOAD_FIELD"; + public const string LEN = "LEN"; + public const string LIMIT = "LIMIT"; + public const string LIMITED = "LIMITED"; + public const string LOAD = "LOAD"; + public const string MAX = "MAX"; + public const string MAXIDLE = "MAXIDLE"; public const string MAXTEXTFIELDS = "MAXTEXTFIELDS"; - public const string TEMPORARY = "TEMPORARY"; - public const string NOOFFSETS = "NOOFFSETS"; - public const string NOHL = "NOHL"; + public const string NOCONTENT = "NOCONTENT"; public const string NOFIELDS = "NOFIELDS"; public const string NOFREQS = "NOFREQS"; - public const string STOPWORDS = "STOPWORDS"; - public const string SKIPINITIALSCAN = "SKIPINITIALSCAN"; - public const string INCLUDE = "INCLUDE"; - public const string EXCLUDE = "EXCLUDE"; - public const string DIALECT = "DIALECT"; - public const string TERMS = "TERMS"; - public const string DISTANCE = "DISTANCE"; - public const string INCR = "INCR"; - public const string PAYLOAD = "PAYLOAD"; - public const string FUZZY = "FUZZY"; - public const string WITHSCORES = "WITHSCORES"; - public const string WITHPAYLOADS = "WITHPAYLOADS"; - public const string MAX = "MAX"; - public const string VERBATIM = "VERBATIM"; - public const string NOCONTENT = "NOCONTENT"; + public const string NOHL = "NOHL"; + public const string NOINDEX = "NOINDEX"; + public const string NOOFFSETS = "NOOFFSETS"; + public const string NOSTEM = "NOSTEM"; public const string NOSTOPWORDS = "NOSTOPWORDS"; + public const string ON_HASH = "ON HASH"; + public const string PARAMS = "PARAMS"; + public const string PAYLOAD = "PAYLOAD"; + public const string PAYLOAD_FIELD = "PAYLOAD_FIELD"; + public const string PHONETIC = "PHONETIC"; + public const string PREFIX = "PREFIX"; + public const string QUERY = "QUERY"; + public const string RETURN = "RETURN"; + public const string SCORE = "SCORE"; + public const string SCORE_FIELD = "SCORE_FIELD"; public const string SCORER = "SCORER"; - public const string INFIELDS = "INFIELDS"; - public const string SORTBY = "SORTBY"; - public const string ASC = "ASC"; - public const string DESC = "DESC"; - public const string LIMIT = "LIMIT"; - public const string HIGHLIGHT = "HIGHLIGHT"; - public const string FIELDS = "FIELDS"; - public const string TAGS = "TAGS"; - public const string SUMMARIZE = "SUMMARIZE"; - public const string FRAGS = "FRAGS"; - public const string LEN = "LEN"; + public const string SEARCH = "SEARCH"; public const string SEPARATOR = "SEPARATOR"; - public const string INKEYS = "INKEYS"; - public const string RETURN = "RETURN"; - public const string PARAMS = "PARAMS"; + public const string SKIPINITIALSCAN = "SKIPINITIALSCAN"; public const string SLOP = "SLOP"; + public const string SORTBY = "SORTBY"; + public const string STOPWORDS = "STOPWORDS"; + public const string SUMMARIZE = "SUMMARIZE"; + public const string TAGS = "TAGS"; + public const string TEMPORARY = "TEMPORARY"; + public const string TERMS = "TERMS"; public const string TIMEOUT = "TIMEOUT"; - public const string INORDER = "INORDER"; - public const string EXPANDER = "EXPANDER"; - public const string SEARCH = "SEARCH"; - public const string AGGREGATE = "AGGREGATE"; - public const string LIMITED = "LIMITED"; - public const string QUERY = "QUERY"; + public const string UNF = "UNF"; + public const string VERBATIM = "VERBATIM"; + public const string WEIGHT = "WEIGHT"; + public const string WITHCURSOR = "WITHCURSOR"; + public const string WITHPAYLOADS = "WITHPAYLOADS"; + public const string WITHSCORES = "WITHSCORES"; + public const string WITHSUFFIXTRIE = "WITHSUFFIXTRIE"; } -} \ No newline at end of file +} diff --git a/src/NRedisStack/Search/Schema.cs b/src/NRedisStack/Search/Schema.cs index 8aaabd08..b0d379ee 100644 --- a/src/NRedisStack/Search/Schema.cs +++ b/src/NRedisStack/Search/Schema.cs @@ -88,20 +88,20 @@ public TextField(string name, double weight = 1.0, bool noStem = false, internal override void AddFieldTypeArgs(List args) { - if (NoStem) args.Add("NOSTEM"); - if (NoIndex) args.Add("NOINDEX"); + if (NoStem) args.Add(SearchArgs.NOSTEM); + if (NoIndex) args.Add(SearchArgs.NOINDEX); AddPhonetic(args); AddWeight(args); - if (WithSuffixTrie) args.Add("WITHSUFFIXTRIE"); + if (WithSuffixTrie) args.Add(SearchArgs.WITHSUFFIXTRIE); if (Sortable) args.Add(AttributeOptions.SORTABLE); - if (Unf) args.Add("UNF"); + if (Unf) args.Add(SearchArgs.UNF); } private void AddWeight(List args) { if (Weight != 1.0) { - args.Add("WEIGHT"); + args.Add(SearchArgs.WEIGHT); args.Add(Weight); } } @@ -110,7 +110,7 @@ private void AddPhonetic(List args) { if (Phonetic != null) { - args.Add("PHONETIC"); + args.Add(SearchArgs.PHONETIC); args.Add(this.Phonetic); } } @@ -145,17 +145,17 @@ internal TagField(string name, bool sortable = false, bool unf = false, internal override void AddFieldTypeArgs(List args) { - if (NoIndex) args.Add("NOINDEX"); - if (WithSuffixTrie) args.Add("WITHSUFFIXTRIE"); + if (NoIndex) args.Add(SearchArgs.NOINDEX); + if (WithSuffixTrie) args.Add(SearchArgs.WITHSUFFIXTRIE); if (Separator != ",") { - args.Add("SEPARATOR"); + args.Add(SearchArgs.SEPARATOR); args.Add(Separator); } - if (CaseSensitive) args.Add("CASESENSITIVE"); + if (CaseSensitive) args.Add(SearchArgs.CASESENSITIVE); if (Sortable) args.Add(AttributeOptions.SORTABLE); - if (Unf) args.Add("UNF"); + if (Unf) args.Add(SearchArgs.UNF); } } @@ -175,7 +175,7 @@ internal GeoField(string name, bool sortable = false, bool noIndex = false) internal override void AddFieldTypeArgs(List args) { - if (NoIndex) args.Add("NOINDEX"); + if (NoIndex) args.Add(SearchArgs.NOINDEX); if (Sortable) args.Add(AttributeOptions.SORTABLE); } @@ -228,7 +228,7 @@ internal NumericField(string name, bool sortable = false, bool noIndex = false) internal override void AddFieldTypeArgs(List args) { - if (NoIndex) args.Add("NOINDEX"); + if (NoIndex) args.Add(SearchArgs.NOINDEX); if (Sortable) args.Add(AttributeOptions.SORTABLE); } diff --git a/tests/NRedisStack.Tests/Search/SearchTests.cs b/tests/NRedisStack.Tests/Search/SearchTests.cs index 7ab708d1..0376a54c 100644 --- a/tests/NRedisStack.Tests/Search/SearchTests.cs +++ b/tests/NRedisStack.Tests/Search/SearchTests.cs @@ -331,17 +331,14 @@ public async Task TestAggregationRequestParamsDialectAsync() 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) .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); @@ -2981,4 +2978,16 @@ public async Task GeoShapeFilterFlatAsync() Assert.Equal(2, res.TotalResults); Assert.Equal(2, res.Documents.Count); } + + [Fact] + public void Issue230() + { + var request = new AggregationRequest("*", 3).Filter("@StatusId==1") + .GroupBy("@CreatedDay", Reducers.CountDistinct("@UserId"), Reducers.Count().As("count")); + + var buildCommand = SearchCommandBuilder.Aggregate("idx:users", request); + // expected: FT.AGGREGATE idx:users * FILTER @StatusId==1 GROUPBY 1 @CreatedDay REDUCE COUNT_DISTINCT 1 @UserId REDUCE COUNT 0 AS count DIALECT 3 + Assert.Equal("FT.AGGREGATE", buildCommand.Command); + Assert.Equal(new object[] { "idx:users", "*", "FILTER", "@StatusId==1", "GROUPBY", 1, "@CreatedDay", "REDUCE", "COUNT_DISTINCT", 1, "@UserId", "REDUCE", "COUNT", 0, "AS", "count", "DIALECT", 3 }, buildCommand.Args); + } }