Exemplo n.º 1
0
        /// <inheritdoc cref="ISearchStore{TDocument}.AdvancedCount" />
        public long AdvancedCount(AdvancedQueryInput input)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }

            /* Requête de filtrage, qui inclus ici le filtre et le post-filtre puisqu'on ne fait pas d'aggrégations. */
            var filterQuery = _builder.BuildAndQuery(GetFilterQuery(input), GetPostFilterSubQuery(input));
            var hasFilter   = !string.IsNullOrEmpty(filterQuery);

            var res = this.GetClient()
                      .Count <TDocument>(s => {
                /* Index / type document. */
                s
                .Index(_indexName)
                .Type(_documentTypeName);

                /* Critère de filtrage. */
                if (hasFilter)
                {
                    s.Query(q => q.QueryString(qs => qs.Query(filterQuery)));
                }

                return(s);
            });

            res.CheckStatus("AdvancedCount");

            return(res.Count);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Obtient le nom du champ pour le groupement.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Nom du champ.</returns>
        private string GetGroupFieldName(AdvancedQueryInput input)
        {
            var groupFacetName = input.ApiInput.Group;

            /* Pas de groupement. */
            if (string.IsNullOrEmpty(groupFacetName))
            {
                return(null);
            }

            /* Recherche de la facette de groupement. */
            var facetDef = input.FacetQueryDefinition.Facets.SingleOrDefault(x => x.Code == groupFacetName);

            if (facetDef == null)
            {
                throw new ElasticException("No facet " + groupFacetName + " to group on.");
            }

            var fieldName = facetDef.FieldName;

            /* Vérifie la présence du champ. */
            if (!_definition.Fields.HasProperty(fieldName))
            {
                throw new ElasticException("The Document \"" + _definition.DocumentTypeName + "\" is missing a \"" + fieldName + "\" property to group on.");
            }

            return(_definition.Fields[fieldName].FieldName);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Obtient la liste des facettes.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Définitions de facettes.</returns>
        private ICollection <IFacetDefinition> GetFacetDefinitionList(AdvancedQueryInput input)
        {
            var groupFacetName = input.ApiInput.Group;
            var list           = input.FacetQueryDefinition != null ? input.FacetQueryDefinition.Facets : new List <IFacetDefinition>();

            /* Recherche de la facette de groupement. */
            string groupFieldName = null;

            if (!string.IsNullOrEmpty(groupFacetName))
            {
                var groupFacetDef = input.FacetQueryDefinition.Facets.SingleOrDefault(x => x.Code == groupFacetName);
                if (groupFacetDef == null)
                {
                    throw new ElasticException("No facet \"" + groupFacetName + "\" to group on.");
                }

                groupFieldName = groupFacetDef.FieldName;
            }

            foreach (var facetDef in list)
            {
                /* Vérifie que le champ à facetter existe sur le document. */
                GetHandler(facetDef).CheckFacet(facetDef);
            }

            return(list);
        }
Exemplo n.º 4
0
        /// <summary>
        /// Créé la requête de filtrage.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Requête de filtrage.</returns>
        private string GetFilterQuery(AdvancedQueryInput input)
        {
            var textSubQuery             = GetTextSubQuery(input);
            var securitySubQuery         = GetSecuritySubQuery(input);
            var filterSubQuery           = GetFilterSubQuery(input);
            var monoValuedFacetsSubQuery = GetFacetSelectionSubQuery(input);

            return(_builder.BuildAndQuery(textSubQuery, securitySubQuery, filterSubQuery, monoValuedFacetsSubQuery));
        }
Exemplo n.º 5
0
        /// <summary>
        /// Crée la sous requête pour les filtres custom.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Sous-requête.</returns>
        private string GetCustomSubQuery(AdvancedQueryInput input)
        {
            if (input.CustomQuery == null)
            {
                return(string.Empty);
            }

            return(input.CustomQuery);
        }
Exemplo n.º 6
0
 /// <inheritdoc cref="ISearchBroker{TDocument}.AdvancedCount" />
 public long AdvancedCount(AdvancedQueryInput input)
 {
     StartProcess(nameof(AdvancedCount));
     try {
         return(_broker.AdvancedCount(input));
     } finally {
         StopProcess();
     }
 }
Exemplo n.º 7
0
 /// <inheritdoc cref="ISearchBroker{TDocument}.AdvancedQuery" />
 public QueryOutput <TDocument> AdvancedQuery(AdvancedQueryInput input)
 {
     StartProcess(nameof(AdvancedQuery));
     try {
         return(_broker.AdvancedQuery(input));
     } finally {
         StopProcess();
     }
 }
Exemplo n.º 8
0
        public void Check_Group()
        {
            var facetQueryDefinition = new FacetQueryDefinition();

            facetQueryDefinition.Facets.Add(new BooleanFacet {
                Name = GenreFacet, FieldName = GenreField
            });

            var input = new AdvancedQueryInput {
                ApiInput = new QueryInput {
                    Criteria = new Criteria(),
                    Skip     = 0,
                    Top      = 10,
                    Group    = GenreFacet
                },
                FacetQueryDefinition = facetQueryDefinition
            };
            var broker = SearchBrokerManager.GetBroker <PersonneDocument>();
            var output = broker.AdvancedQuery(input);

            /* Total. */
            Assert.AreEqual(6, output.TotalCount, "Nombre total de résultats attendu incorrect.");

            /* Groupes */
            var groups = output.Groups;

            Assert.IsNotNull(groups);
            Assert.AreEqual(3, groups.Count, "Nombre de groupes attendu incorrect.");

            foreach (var group in groups)
            {
                var bucket = group.Value;
                switch (group.Key)
                {
                case "M":
                    Assert.AreEqual(3, bucket.Count);
                    break;

                case "F":
                    Assert.AreEqual(2, bucket.Count);
                    break;

                case FacetConst.NullValue:
                    Assert.AreEqual(1, bucket.Count);
                    break;

                default:
                    Assert.Fail("Clée inattendue : " + group.Key);
                    break;
                }
            }
        }
Exemplo n.º 9
0
        /// <summary>
        /// Crée la sous requête pour les champs de filtre.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Sous-requête.</returns>
        private string GetFilterSubQuery(AdvancedQueryInput input)
        {
            if (input.FilterList == null || !input.FilterList.Any())
            {
                return(string.Empty);
            }

            var filterList = new List <string>();

            foreach (KeyValuePair <string, string> entry in input.FilterList)
            {
                var field = _definition.Fields[entry.Key].FieldName;
                filterList.Add(_builder.BuildFilter(field, entry.Value));
            }

            return(_builder.BuildAndQuery(filterList.ToArray()));
        }
Exemplo n.º 10
0
        /// <inheritdoc cref="ISearchBroker{TDocument}.Query" />
        public IEnumerable <TDocument> Query(string text, string security = null)
        {
            if (string.IsNullOrEmpty(text))
            {
                return(new List <TDocument>());
            }

            var input = new AdvancedQueryInput {
                ApiInput = new QueryInput {
                    Criteria = text,
                    Skip     = 0,
                    Top      = QueryDefaultSize
                },
                Security = security
            };
            var output = _store.AdvancedQuery(input);

            return(output.List);
        }
Exemplo n.º 11
0
        /// <summary>
        /// Créé la sous-requête le filtrage par sélection de facette.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Sous-requête.</returns>
        private string GetFacetSelectionSubQuery(AdvancedQueryInput input)
        {
            var facetList = input.ApiInput.Facets;

            if (facetList == null || !facetList.Any())
            {
                return(string.Empty);
            }

            var facetSubQueryList =
                facetList.Select(f => {
                /* Récupère la définition de la facette. */
                var def = input.FacetQueryDefinition.Facets.Single(x => x.Name == f.Key);
                /* Créé une sous-requête par facette. */
                string s = f.Value;
                return(_handler.CreateFacetSubQuery(s, def));
            }).ToArray();

            /* Concatène en "ET" toutes les sous-requêtes. */
            return(_builder.BuildAndQuery(facetSubQueryList));
        }
Exemplo n.º 12
0
        /// <summary>
        /// Créé la sous-requête pour le champ textuel.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Sous-requête.</returns>
        private string GetTextSubQuery(AdvancedQueryInput input)
        {
            var value = input.ApiInput.Criteria;

            /* Absence de texte ou joker : sous-requête vide. */
            if (string.IsNullOrEmpty(value) || value == "*")
            {
                return(string.Empty);
            }

            /* Vérifie la présence d'un champ textuel. */
            var fieldDesc = _definition.TextField;

            if (fieldDesc == null)
            {
                throw new ElasticException("The Document \"" + _definition.DocumentTypeName + "\" needs a Search category field to allow Query.");
            }

            /* Constuit la sous requête. */
            return(_builder.BuildFullTextSearch(fieldDesc.FieldName, value));
        }
Exemplo n.º 13
0
        /// <summary>
        /// Créé la sous-requête le filtrage de sécurité.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Sous-requête.</returns>
        private string GetSecuritySubQuery(AdvancedQueryInput input)
        {
            var value = input.Security;

            /* Absence de filtrage de sécurité : sous-requêt vide. */
            if (string.IsNullOrEmpty(value))
            {
                return(string.Empty);
            }

            /* Vérifie la présence d'un champ de sécurité. */
            var fieldDesc = _definition.SecurityField;

            if (fieldDesc == null)
            {
                throw new ElasticException("The Document \"" + _definition.DocumentTypeName + "\" needs a Security category field to allow Query with security filtering.");
            }

            /* Constuit la sous requête. */
            return(_builder.BuildInclusiveInclude(fieldDesc.FieldName, value));
        }
Exemplo n.º 14
0
        private static QueryOutput <PersonneDocument> CheckFacets(FacetListInput facetsInput, string query = null)
        {
            var facetQueryDefinition = new FacetQueryDefinition(new BooleanFacet {
                Name      = GenreFacet,
                FieldName = GenreField
            });
            var input = new AdvancedQueryInput {
                ApiInput = new QueryInput {
                    Criteria = new Criteria {
                        Query = query
                    },
                    Skip   = 0,
                    Top    = 10,
                    Facets = facetsInput,
                },
                FacetQueryDefinition = facetQueryDefinition
            };
            var broker = SearchBrokerManager.GetBroker <PersonneDocument>();

            return(broker.AdvancedQuery(input));
        }
Exemplo n.º 15
0
        /// <summary>
        /// Obtient la définition du tri.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Définition du tri.</returns>
        private SortDefinition GetSortDefinition(AdvancedQueryInput input)
        {
            var fieldName = input.ApiInput.SortFieldName;

            /* Cas de l'absence de tri. */
            if (string.IsNullOrEmpty(fieldName))
            {
                return(new SortDefinition());
            }

            /* Vérifie la présence du champ. */
            if (!_definition.Fields.HasProperty(fieldName))
            {
                throw new ElasticException("The Document \"" + _definition.DocumentTypeName + "\" is missing a \"" + fieldName + "\" property to sort on.");
            }

            return(new SortDefinition {
                FieldName = _definition.Fields[fieldName].FieldName,
                Order = input.ApiInput.SortDesc ? SortOrder.Descending : SortOrder.Ascending
            });
        }
Exemplo n.º 16
0
        private static void CheckFacets(long expected, FacetListInput facetsInput, string query = null)
        {
            var facetQueryDefinition = new FacetQueryDefinition(new BooleanFacet {
                Code      = GenreFacet,
                FieldName = GenreField
            });
            var input = new AdvancedQueryInput {
                ApiInput = new QueryInput {
                    Criteria = new Criteria {
                        Query = query
                    },
                    Skip   = 0,
                    Top    = 10,
                    Facets = facetsInput,
                },
                FacetQueryDefinition = facetQueryDefinition
            };
            var broker = SearchBrokerManager.GetBroker <PersonneDocument>();
            var actual = broker.AdvancedCount(input);

            Assert.AreEqual(expected, actual, "Le compte n'est pas bon.");
        }
Exemplo n.º 17
0
        private static QueryOutput <PersonneDocument> CheckFacets(FacetListInput facetsInput, string query = null, string security = null, string portfolio = null)
        {
            var facetQueryDefinition = new FacetQueryDefinition(new PortfolioFacet {
                Code      = DepartementFacet,
                FieldName = DepartementField
            });
            var input = new AdvancedQueryInput {
                ApiInput = new QueryInput {
                    Criteria = new Criteria {
                        Query = query
                    },
                    Skip   = 0,
                    Top    = 10,
                    Facets = facetsInput,
                },
                FacetQueryDefinition = facetQueryDefinition,
                Security             = security,
                Portfolio            = portfolio
            };
            var broker = SearchBrokerManager.GetBroker <PersonneDocument>();

            return(broker.AdvancedQuery(input));
        }
Exemplo n.º 18
0
        private static void Check_Sort(bool sortDescending, IEnumerable <string> expectedNomList)
        {
            var input = new AdvancedQueryInput {
                ApiInput = new QueryInput {
                    Criteria       = new Criteria(),
                    Skip           = 0,
                    Top            = 10,
                    SortFieldName  = "NomSort",
                    SortDescending = sortDescending
                }
            };
            var broker = SearchBrokerManager.GetBroker <PersonneDocument>();
            var output = broker.AdvancedQuery(input);

            /* Total. */
            Assert.AreEqual(6, output.List.Count, "Nombre de résultats attendu incorrect.");
            Assert.AreEqual(6, output.TotalCount, "Nombre total de résultats attendu incorrect.");

            var nomList = output.List.Select(x => x.Nom);

            Assert.IsTrue(
                Enumerable.SequenceEqual(expectedNomList, nomList),
                "Tri attendu : " + string.Join(",", expectedNomList) + Environment.NewLine + " | Tri constaté : " + string.Join(",", nomList));
        }
Exemplo n.º 19
0
        /// <summary>
        /// Créé la sous-requête de post-filtrage pour les facettes multi-sélectionnables.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Sous-requête.</returns>
        private string GetPostFilterSubQuery(AdvancedQueryInput input)
        {
            var facetList = input.ApiInput.Facets;

            if (facetList == null || !facetList.Any())
            {
                return(string.Empty);
            }

            /* Créé une sous-requête par facette */
            var facetSubQueryList = facetList
                                    .Select(f => {
                /* Récupère la définition de la facette multi-sélectionnable. */
                var def = input.FacetQueryDefinition.Facets.SingleOrDefault(x => x.IsMultiSelectable == true && x.Code == f.Key);
                if (def == null)
                {
                    return(null);
                }

                var handler = GetHandler(def);
                /* On fait un "OR" sur toutes les valeurs sélectionnées. */
                return(_builder.BuildOrQuery(f.Value.Select(s => handler.CreateFacetSubQuery(s, def, input.Portfolio)).ToArray()));
            })
                                    .Where(f => f != null)
                                    .ToArray();

            if (facetSubQueryList.Any())
            {
                /* Concatène en "ET" toutes les sous-requêtes. */
                return(_builder.BuildAndQuery(facetSubQueryList));
            }
            else
            {
                return(string.Empty);
            }
        }
Exemplo n.º 20
0
        /// <summary>
        /// Créé la sous-requête le filtrage par sélection de facette non multi-sélectionnables.
        /// </summary>
        /// <param name="input">Entrée.</param>
        /// <returns>Sous-requête.</returns>
        private string GetFacetSelectionSubQuery(AdvancedQueryInput input)
        {
            var facetList = input.ApiInput.Facets;

            if (facetList == null || !facetList.Any())
            {
                return(string.Empty);
            }

            /* Créé une sous-requête par facette. */
            var facetSubQueryList = facetList
                                    .Select(f => {
                /* Récupère la définition de la facette non multi-sélectionnable. */
                var def = input.FacetQueryDefinition.Facets.SingleOrDefault(x => x.IsMultiSelectable == false && x.Code == f.Key);
                if (def == null)
                {
                    return(null);
                }

                /* La facette n'est pas multi-sélectionnable donc on prend direct la première valeur. */
                var s = f.Value[0];
                return(GetHandler(def).CreateFacetSubQuery(s, def, input.Portfolio));
            })
                                    .Where(f => f != null)
                                    .ToArray();

            if (facetSubQueryList.Any())
            {
                /* Concatène en "ET" toutes les sous-requêtes. */
                return(_builder.BuildAndQuery(facetSubQueryList));
            }
            else
            {
                return(string.Empty);
            }
        }
Exemplo n.º 21
0
 /// <inheritdoc cref="ISearchBroker{TDocument}.AdvancedQuery" />
 public QueryOutput <TDocument> AdvancedQuery(AdvancedQueryInput input)
 {
     return(_store.AdvancedQuery(input));
 }
Exemplo n.º 22
0
 /// <inheritdoc cref="ISearchBroker{TDocument}.AdvancedCount" />
 public long AdvancedCount(AdvancedQueryInput input)
 {
     return(_store.AdvancedCount(input));
 }
Exemplo n.º 23
0
        /// <inheritdoc cref="ISearchStore{TDocument}.AdvancedQuery" />
        public QueryOutput <TDocument> AdvancedQuery(AdvancedQueryInput input)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            var apiInput = input.ApiInput;

            /* Tri */
            var sortDef = GetSortDefinition(input);

            /* Requête de filtrage. */
            var textSubQuery      = GetTextSubQuery(input);
            var securitySubQuery  = GetSecuritySubQuery(input);
            var facetSubQuery     = GetFacetSelectionSubQuery(input);
            var filterSubQuery    = GetFilterSubQuery(input);
            var conditionSubQuery = GetConditionSubQuery(input);
            var customSubQuery    = GetCustomSubQuery(input);
            var filterQuery       = _builder.BuildAndQuery(textSubQuery, securitySubQuery, facetSubQuery, filterSubQuery, conditionSubQuery, customSubQuery);
            var hasFilter         = !string.IsNullOrEmpty(filterQuery);

            /* Facettage. */
            var facetDefList = GetFacetDefinitionList(input);
            var hasFacet     = facetDefList.Any();

            /* Group */
            var groupFieldName = GetGroupFieldName(input);
            var hasGroup       = !string.IsNullOrEmpty(apiInput.Group);

            /* Pagination. */
            var skip = apiInput.Skip ?? 0;
            var size = hasGroup ? 0 : apiInput.Top ?? 100000; // TODO Paramétrable ?

            var res = this.GetClient()
                      .Search <TDocument>(s => {
                s
                /* Index / type document. */
                .Index(_indexName)
                .Type(_documentTypeName)

                /* Pagination */
                .From(skip)
                .Size(size);

                /* Tri */
                if (sortDef.HasSort)
                {
                    s.Sort(x => x
                           .OnField(sortDef.FieldName)
                           .Order(sortDef.Order));
                }

                /* Critère de filtrage. */
                if (hasFilter)
                {
                    s.Query(q =>
                            q.QueryString(qs => qs
                                          .Query(filterQuery)));
                }

                /* Aggrégations. */
                if (hasFacet || hasGroup)
                {
                    s.Aggregations(a => {
                        if (hasFacet)
                        {
                            /* Facettage. */
                            foreach (var facetDef in facetDefList)
                            {
                                _handler.DefineAggregation(a, facetDef);
                            }
                        }
                        if (hasGroup)
                        {
                            /* Groupement. */
                            a.Terms(groupFieldName, st => st
                                    .Field(groupFieldName)
                                    .Size(10)
                                    .Aggregations(g => g
                                                  .TopHits(_topHitName,
                                                           x => {
                                x.Size(10);
                                if (sortDef.HasSort)
                                {
                                    x.Sort(t => t
                                           .OnField(sortDef.FieldName)
                                           .Order(sortDef.Order));
                                }
                                return(x);
                            })));
                            /* Groupement pour les valeurs nulles */
                            a.Missing(groupFieldName + MissingGroupPrefix, st => st
                                      .Field(groupFieldName)
                                      .Aggregations(g => g
                                                    .TopHits(_topHitName, x => x.Size(10))));
                        }
                        return(a);
                    });
                }

                return(s);
            });

            res.CheckStatus("AdvancedQuery");

            /* Extraction des facettes. */
            var facetListOutput = new List <FacetOutput>();

            if (hasFacet)
            {
                var aggs = res.Aggs;
                foreach (var facetDef in facetDefList)
                {
                    FacetOutput facetOutput = _handler.ExtractFacetOutput(aggs, facetDef);
                    facetOutput.Code  = facetDef.Name;
                    facetOutput.Label = facetDef.Label;
                    facetListOutput.Add(facetOutput);
                }
            }

            /* Ajout des facettes manquantes */
            if (input.ApiInput.Facets != null)
            {
                foreach (var facet in input.ApiInput.Facets)
                {
                    FacetOutput facetOutput = facetListOutput.Where(f => f.Code == facet.Key).First();
                    if (!facetOutput.Values.Any(f => f.Code == facet.Value))
                    {
                        facetOutput.Values.Add(new Kinetix.ComponentModel.SearchV3.FacetItem {
                            Code  = facet.Value,
                            Label = facetDefList.FirstOrDefault(fct => fct.Name == facet.Key)?.ResolveLabel(facet.Value),
                            Count = 0
                        });
                    }
                }
            }

            /* Extraction des résultats. */
            var resultList      = new List <TDocument>();
            var groupResultList = new List <GroupResult <TDocument> >();

            if (hasGroup)
            {
                /* Groupement. */
                var bucket = (Bucket)res.Aggregations[groupFieldName];
                foreach (KeyItem group in bucket.Items)
                {
                    var groupName = group.Key;
                    var topHitAgg = (TopHitsMetric)group.Aggregations[_topHitName];
                    var docs      = topHitAgg.Documents <TDocument>().ToList();

                    groupResultList.Add(new GroupResult <TDocument>()
                    {
                        Code  = groupName,
                        Label = facetDefList.FirstOrDefault(fct => fct.Name == apiInput.Group)?.ResolveLabel(groupName),
                        List  = docs
                    });
                }

                /* Groupe pour les valeurs null. */
                var nullBucket    = (SingleBucket)res.Aggregations[groupFieldName + MissingGroupPrefix];
                var nullTopHitAgg = (TopHitsMetric)nullBucket.Aggregations[_topHitName];
                var nullDocs      = nullTopHitAgg.Documents <TDocument>().ToList();
                if (nullDocs.Any())
                {
                    groupResultList.Add(new GroupResult <TDocument>()
                    {
                        Code = FacetConst.NullValue, List = nullDocs
                    });
                }

                resultList = null;
            }
            else
            {
                /* Liste unique. */
                resultList      = res.Documents.ToList();
                groupResultList = null;
            }

            /* Construction de la sortie. */
            var output = new QueryOutput <TDocument> {
                List       = resultList,
                Facets     = facetListOutput,
                Groups     = groupResultList,
                Query      = apiInput,
                TotalCount = res.Total
            };

            return(output);
        }
Exemplo n.º 24
0
        /// <inheritdoc cref="ISearchStore{TDocument}.AdvancedQuery" />
        public QueryOutput <TDocument> AdvancedQuery(AdvancedQueryInput input)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }

            var apiInput = input.ApiInput;

            /* Tri */
            var sortDef = GetSortDefinition(input);

            /* Requêtes de filtrage. */
            var filterQuery     = GetFilterQuery(input);
            var hasFilter       = !string.IsNullOrEmpty(filterQuery);
            var postFilterQuery = GetPostFilterSubQuery(input);
            var hasPostFilter   = !string.IsNullOrEmpty(postFilterQuery);

            /* Facettage. */
            var facetDefList = GetFacetDefinitionList(input);
            var hasFacet     = facetDefList.Any();
            var portfolio    = input.Portfolio;

            /* Group */
            var groupFieldName = GetGroupFieldName(input);
            var hasGroup       = !string.IsNullOrEmpty(apiInput.Group);

            /* Pagination. */
            var skip = apiInput.Skip ?? 0;
            var size = hasGroup ? 0 : apiInput.Top ?? 1000; // TODO Paramétrable ?

            var res = this.GetClient()
                      .Search <TDocument>(s => {
                s
                /* Index / type document. */
                .Index(_indexName)
                .Type(_documentTypeName)

                /* Pagination */
                .From(skip)
                .Size(size);

                /* Tri */
                if (sortDef.HasSort)
                {
                    s.Sort(x => x
                           .Field(sortDef.FieldName, sortDef.Order));
                }

                /* Critère de filtrage. */
                if (hasFilter)
                {
                    s.Query(q => q.QueryString(qs => qs.Query(filterQuery)));
                }

                /* Critère de post-filtrage. */
                if (hasPostFilter)
                {
                    s.PostFilter(q => q.QueryString(qs => qs.Query(postFilterQuery)));
                }

                /* Aggrégations. */
                if (hasFacet || hasGroup)
                {
                    s.Aggregations(a => {
                        if (hasFacet)
                        {
                            /* Facettage. */
                            foreach (var facetDef in facetDefList)
                            {
                                GetHandler(facetDef).DefineAggregation(a, facetDef, facetDefList, input.ApiInput.Facets, portfolio);
                            }
                        }
                        if (hasGroup)
                        {
                            /* Groupement. */
                            a.Filter(GroupAggs, f => {
                                /* Critère de post-filtrage répété sur les groupes, puisque ce sont des agrégations qui par définition ne sont pas affectées par le post-filtrage. */
                                if (hasPostFilter)
                                {
                                    f.Filter(q => q.QueryString(qs => qs.Query(postFilterQuery)));
                                }

                                return(f.Aggregations(aa => aa
                                                      /* Groupement. */
                                                      .Terms(groupFieldName, st => st
                                                             .Field(groupFieldName)
                                                             .Aggregations(g => g.TopHits(_topHitName, x => x.Size(input.GroupSize))))
                                                      /* Groupement pour les valeurs nulles */
                                                      .Missing(groupFieldName + MissingGroupPrefix, st => st
                                                               .Field(groupFieldName)
                                                               .Aggregations(g => g.TopHits(_topHitName, x => x.Size(input.GroupSize))))));
                            });
                        }
                        return(a);
                    });
                }

                return(s);
            });

            res.CheckStatus("AdvancedQuery");

            /* Extraction des facettes. */
            var facetListOutput = new List <FacetOutput>();

            if (hasFacet)
            {
                var aggs = res.Aggs;
                foreach (var facetDef in facetDefList)
                {
                    facetListOutput.Add(new FacetOutput {
                        Code              = facetDef.Code,
                        Label             = facetDef.Label,
                        IsMultiSelectable = facetDef.IsMultiSelectable,
                        Values            = GetHandler(facetDef).ExtractFacetItemList(aggs, facetDef, res.Total)
                    });
                }
            }

            /* Ajout des valeurs de facettes manquantes (cas d'une valeur demandée par le client non trouvée par la recherche.) */
            if (input.ApiInput.Facets != null)
            {
                foreach (var facet in input.ApiInput.Facets)
                {
                    var facetItems = facetListOutput.Single(f => f.Code == facet.Key).Values;
                    /* On ajoute un FacetItem par valeur non trouvée, avec un compte de 0. */
                    foreach (var value in facet.Value)
                    {
                        if (!facetItems.Any(f => f.Code == value))
                        {
                            facetItems.Add(new FacetItem {
                                Code  = value,
                                Label = facetDefList.FirstOrDefault(fct => fct.Code == facet.Key)?.ResolveLabel(value),
                                Count = 0
                            });
                        }
                    }
                }
            }

            /* Extraction des résultats. */
            var resultList      = new List <TDocument>();
            var groupResultList = new List <GroupResult <TDocument> >();

            if (hasGroup)
            {
                /* Groupement. */
                var bucket = (BucketAggregate)res.Aggs.Filter(GroupAggs).Aggregations[groupFieldName];
                foreach (KeyedBucket <object> group in bucket.Items)
                {
                    var list = ((TopHitsAggregate)group.Aggregations[_topHitName]).Documents <TDocument>().ToList();
                    groupResultList.Add(new GroupResult <TDocument> {
                        Code       = group.Key.ToString(),
                        Label      = facetDefList.First(f => f.Code == apiInput.Group).ResolveLabel(group.Key),
                        List       = list,
                        TotalCount = (int)group.DocCount
                    });
                }

                /* Groupe pour les valeurs null. */
                var nullBucket    = (SingleBucketAggregate)res.Aggs.Filter(GroupAggs).Aggregations[groupFieldName + MissingGroupPrefix];
                var nullTopHitAgg = (TopHitsAggregate)nullBucket.Aggregations[_topHitName];
                var nullDocs      = nullTopHitAgg.Documents <TDocument>().ToList();
                if (nullDocs.Any())
                {
                    groupResultList.Add(new GroupResult <TDocument> {
                        Code       = FacetConst.NullValue,
                        Label      = input.FacetQueryDefinition.FacetNullValueLabel ?? "focus.search.results.missing",
                        List       = nullDocs,
                        TotalCount = (int)nullBucket.DocCount
                    });
                }

                resultList = null;
            }
            else
            {
                /* Liste unique. */
                resultList      = res.Documents.ToList();
                groupResultList = null;
            }

            /* Construction de la sortie. */
            var output = new QueryOutput <TDocument> {
                List       = resultList,
                Facets     = facetListOutput,
                Groups     = groupResultList,
                Query      = apiInput,
                TotalCount = res.Total
            };

            return(output);
        }