public async Task <long> GetRanking(ScoreRecord score, LeaderboardQuery filters, string leaderboardName)
        {
            var client = await CreateClient(leaderboardName);

            var rankResult = await client.CountAsync <ScoreRecord>(desc => desc
                                                                   .Query(query =>
                                                                          CreateQuery(query, filters,
                                                                                      q =>
            {
                var mustClauses = new List <Func <Nest.QueryContainerDescriptor <ScoreRecord>, Nest.QueryContainer> >();

                mustClauses.Add(q1 => q1.Range(r => r.Field(record => record.Score).GreaterThan(score.Score)));
                if (!EnableExequo)
                {
                    mustClauses.Add(q1 => q1.Bool(b2 => b2.Must(
                                                      q2 => q2.Term(t => t.Field(record => record.Score).Value(score.Score)),
                                                      q2 => q2.DateRange(r => r.Field(record => record.CreatedOn).LessThan(score.CreatedOn))
                                                      )));
                }
                return(q.Bool(b => b.Should(mustClauses)));
            }
                                                                                      )));

            if (!rankResult.IsValid)
            {
                throw new InvalidOperationException($"Failed to compute rank. {rankResult.ServerError.Error.Reason}");
            }
            return(rankResult.Count + 1);
        }
 internal LeaderboardContinuationQuery(LeaderboardQuery parent)
 {
     StartId      = parent.StartId;
     ScoreFilters = parent.ScoreFilters;
     FieldFilters = parent.FieldFilters;
     FriendsIds   = parent.FriendsIds;
     Count        = parent.Count;
     Skip         = parent.Skip;
     Name         = parent.Name;
 }
        public async Task <long> GetTotal(LeaderboardQuery filters, string leaderboardName)
        {
            var client = await CreateClient(leaderboardName);

            var rankResult = await client.CountAsync <ScoreRecord>(desc => desc
                                                                   .Query(query =>
                                                                          CreateQuery(query, filters)));

            if (!rankResult.IsValid)
            {
                throw new InvalidOperationException($"Failed to compute total scores in filter. {rankResult.ServerError.Error.Reason}");
            }
            return(rankResult.Count);
        }
 private Task <Nest.IElasticClient> CreateClient(LeaderboardQuery rq)
 {
     return(CreateClient(rq.Name));
 }
        private Nest.QueryContainer CreateQuery(
            Nest.QueryContainerDescriptor <ScoreRecord> desc,
            LeaderboardQuery rq,
            Func <Nest.QueryContainerDescriptor <ScoreRecord>, Nest.QueryContainer> additionalContraints = null)
        {
            return(desc.Bool(s2 =>
            {
                var mustClauses = Enumerable.Empty <Func <Nest.QueryContainerDescriptor <ScoreRecord>, Nest.QueryContainer> >();

                if (rq.FriendsIds.Any())
                {
                    mustClauses = mustClauses.Concat(new Func <Nest.QueryContainerDescriptor <ScoreRecord>, Nest.QueryContainer>[] {
                        q => q.Terms(t => t.Field(s => s.PlayerId).Terms(rq.FriendsIds.Select(i => i.ToString())))
                    });
                }
                if (rq.FieldFilters != null && rq.FieldFilters.Any())
                {
                    mustClauses = mustClauses.Concat(rq.FieldFilters.Select <FieldFilter, Func <Nest.QueryContainerDescriptor <ScoreRecord>, Nest.QueryContainer> >(f =>
                    {
                        return q => q.Term("document." + f.Field, f.Value);
                    }));
                }
                if (rq.ScoreFilters != null && rq.ScoreFilters.Any())
                {
                    mustClauses = mustClauses.Concat(rq.ScoreFilters.Select <ScoreFilter, Func <Nest.QueryContainerDescriptor <ScoreRecord>, Nest.QueryContainer> >(f =>
                    {
                        return q => q.Range(r =>
                        {
                            r = r.Field(s3 => s3.Score);
                            switch (f.Type)
                            {
                            case ScoreFilterType.GreaterThan:
                                r = r.GreaterThan(f.Value);
                                break;

                            case ScoreFilterType.GreaterThanOrEqual:
                                r = r.GreaterThanOrEquals(f.Value);
                                break;

                            case ScoreFilterType.LesserThan:
                                r = r.LessThan(f.Value);
                                break;

                            case ScoreFilterType.LesserThanOrEqual:
                                r = r.LessThanOrEquals(f.Value);
                                break;

                            default:
                                break;
                            }

                            return r;
                        });
                    }));
                }
                if (additionalContraints != null)
                {
                    mustClauses = mustClauses.Concat(new[] { additionalContraints });
                }
                return s2.Must(mustClauses);
            }));
        }
        public async Task <LeaderboardResult <ScoreRecord> > Query(LeaderboardQuery rq)
        {
            var isContinuation = rq is LeaderboardContinuationQuery;
            var client         = await CreateClient(rq);

            ScoreRecord start = null;

            if (!string.IsNullOrEmpty(rq.StartId))
            {
                start = await GetScore(rq.StartId, rq.Name);

                if (start == null)
                {
                    throw new ClientException("Admiral not found in leadeboard.");
                }
            }

            var result = await client.SearchAsync <ScoreRecord>(s =>
            {
                s = s.AllowNoIndices();
                s = s.Query(query => CreateQuery(query, rq, q =>
                {
                    if (start != null)//If we have a pivot we must add constraint to start the result around it.
                    {
                        //Create next/previous additional constraints
                        if ((rq as LeaderboardContinuationQuery)?.IsPrevious == true)
                        {
                            return(CreatePreviousPaginationFilter(q, start));
                        }
                        else
                        {
                            return(CreateNextPaginationFilter(q, start));
                        }
                    }
                    else
                    {
                        return(q);
                    }
                })).AllowNoIndices();

                if ((rq as LeaderboardContinuationQuery)?.IsPrevious == true)
                {
                    s = s.Sort(sort => sort.Ascending(record => record.Score).Descending(record => record.CreatedOn));
                }
                else
                {
                    s = s.Sort(sort => sort.Descending(record => record.Score).Ascending(record => record.CreatedOn));
                }
                if ((isContinuation && (rq as LeaderboardContinuationQuery)?.IsPrevious == false) || start == null)
                {
                    s = s.Size(rq.Count + 1).From(rq.Skip);// We get one more document  than necessary to be able to determine if we can build a "next" continuation
                }
                else// The pivot is not included in the result set, if we are not running a continuation query, we must prefix the results with the pivot.
                {
                    s = s.Size(rq.Count).From(rq.Skip);
                }


                return(s);
            });

            if (!result.IsValid)
            {
                if (result.ServerError.Status == 404)
                {
                    return(new LeaderboardResult <ScoreRecord> {
                        Results = new List <LeaderboardRanking <ScoreRecord> >()
                    });
                }
                _logger.Log(LogLevel.Error, "leaderboard", "failed to process query request.", result.ServerError);
                throw new InvalidOperationException($"Failed to query leaderboard : {result.ServerError.Error.Reason}");
            }
            var documents = result.Documents.ToList();

            if (!isContinuation && start != null)
            {
                documents.Insert(0, start);
            }
            else if ((rq as LeaderboardContinuationQuery)?.IsPrevious == true)
            {
                documents.Reverse();
            }

            //Compute rankings
            if (documents.Any())
            {
                int firstRank = 0;
                try
                {
                    firstRank = (int) await GetRanking(documents.First(), rq, rq.Name);
                }
                catch (InvalidOperationException ex)
                {
                    _logger.Log(LogLevel.Error, "leaderboard", ex.Message, ex);
                    throw new InvalidOperationException($"Failed to query leaderboard : {ex.Message}");
                }
                var rank      = firstRank;
                var lastScore = int.MaxValue;
                var lastRank  = firstRank;
                var results   = new List <LeaderboardRanking <ScoreRecord> >();


                foreach (var doc in documents.Take(rq.Count))
                {
                    if (EnableExequo)
                    {
                        int currentRank;
                        if (doc.Score == lastScore)
                        {
                            currentRank = lastRank;
                        }
                        else
                        {
                            currentRank = rank;
                        }

                        results.Add(new LeaderboardRanking <ScoreRecord> {
                            Document = doc, Ranking = currentRank
                        });
                        lastRank = currentRank;
                    }
                    else
                    {
                        results.Add(new LeaderboardRanking <ScoreRecord> {
                            Document = doc, Ranking = rank
                        });
                    }
                    rank++;
                }


                var leaderboardResult = new LeaderboardResult <ScoreRecord> {
                    Results = results
                };

                if (firstRank > 1)//There are scores before the first in the list
                {
                    var previousQuery = new LeaderboardContinuationQuery(rq);
                    previousQuery.Skip         = 0;
                    previousQuery.Count        = rq.Count;
                    previousQuery.IsPrevious   = true;
                    previousQuery.StartId      = results.First().Document.Id;
                    leaderboardResult.Previous = SerializeContinuationQuery(previousQuery);
                }

                if (documents.Count > rq.Count || (rq as LeaderboardContinuationQuery)?.IsPrevious == true)//there are scores after the last in the list.
                {
                    var nextQuery = new LeaderboardContinuationQuery(rq);
                    nextQuery.Skip         = 0;
                    nextQuery.Count        = rq.Count;
                    nextQuery.IsPrevious   = false;
                    nextQuery.StartId      = results.Last().Document.Id;
                    leaderboardResult.Next = SerializeContinuationQuery(nextQuery);
                }

                return(leaderboardResult);
            }
            else
            {
                return(new LeaderboardResult <ScoreRecord>());
            }
        }