11package com.algolia.search.client
22
3+ import com.algolia.search.dsl.filters
34import com.algolia.search.endpoint.*
5+ import com.algolia.search.model.Attribute
46import com.algolia.search.model.IndexName
7+ import com.algolia.search.model.filter.Filter
8+ import com.algolia.search.model.multipleindex.IndexQuery
59import com.algolia.search.model.response.ResponseSearch
610import com.algolia.search.model.response.ResponseSearchRules
711import com.algolia.search.model.response.ResponseSearchSynonyms
12+ import com.algolia.search.model.response.ResponseSearches
813import com.algolia.search.model.rule.Rule
914import com.algolia.search.model.rule.RuleQuery
15+ import com.algolia.search.model.search.Facet
16+ import com.algolia.search.model.search.FacetStats
1017import com.algolia.search.model.search.Query
1118import com.algolia.search.model.synonym.Synonym
1219import com.algolia.search.model.synonym.SynonymQuery
@@ -101,4 +108,112 @@ public data class Index internal constructor(
101108 }
102109 return responses
103110 }
111+
112+ suspend fun searchHierarchical (
113+ query : Query = Query (),
114+ disjunctiveFacets : List <Attribute > = listOf(),
115+ filters : Set <Filter > = setOf(),
116+ hierarchicalAttributes : List <Attribute > = listOf(),
117+ hierarchicalFilters : List <Filter .Facet > = listOf()
118+ ): ResponseSearch {
119+ val (filtersOr, filtersAnd) = filters.partition { disjunctiveFacets.contains(it.attribute) }
120+ val filtersOrFacet = filtersOr.filterIsInstance<Filter .Facet >()
121+ val filtersOrTag = filtersOr.filterIsInstance<Filter .Tag >()
122+ val filtersOrNumeric = filtersOr.filterIsInstance<Filter .Numeric >()
123+ val queryForResults = query
124+ .toIndexQuery()
125+ .filters(filtersAnd, filtersOrFacet, filtersOrTag, filtersOrNumeric)
126+ val queriesForDisjunctiveFacets = disjunctiveFacets.map { attribute ->
127+ query
128+ .toIndexQuery()
129+ .filters(
130+ filtersAnd,
131+ filtersOrFacet.filter { it.attribute != attribute },
132+ filtersOrTag,
133+ filtersOrNumeric
134+ )
135+ .setFacets(attribute)
136+ .optimize()
137+ }
138+ val queriesForHierarchicalFacets = hierarchicalAttributes
139+ .take(hierarchicalFilters.size + 1 )
140+ .mapIndexed { index, attribute ->
141+ query
142+ .toIndexQuery()
143+ .filters(filtersAnd.combine(hierarchicalFilters.getOrNull(index - 1 )).minus(hierarchicalFilters.last()), filtersOrFacet, filtersOrTag, filtersOrNumeric)
144+ .setFacets(attribute)
145+ .optimize()
146+ }
147+ val queries = listOf (queryForResults) + queriesForDisjunctiveFacets + queriesForHierarchicalFacets
148+ val response = EndpointMultipleIndexImpl (transport).multipleQueries(queries)
149+
150+ return response.aggregateResult(disjunctiveFacets.size)
151+ }
152+
153+ private fun List<ResponseSearch>.aggregateFacets (): Map <Attribute , List <Facet >> {
154+ return fold(mapOf ()) { acc, result ->
155+ result.facetsOrNull?.let { acc + it } ? : acc
156+ }
157+ }
158+
159+ private fun List<ResponseSearch>.aggregateFacetStats (): Map <Attribute , FacetStats > {
160+ return fold(mapOf ()) { acc, result ->
161+ result.facetStatsOrNull?.let { acc + it } ? : acc
162+ }
163+ }
164+
165+ private fun List<Filter>.combine (hierarchicalFilter : Filter .Facet ? ): List <Filter > {
166+ return hierarchicalFilter?.let { this + it } ? : this
167+ }
168+
169+ private fun ResponseSearches.aggregateResult (disjunctiveFacetCount : Int ): ResponseSearch {
170+ val resultsDisjunctiveFacets = results.subList(1 , 1 + disjunctiveFacetCount)
171+ val resultHierarchicalFacets = results.subList(1 + disjunctiveFacetCount, results.size)
172+ val facets = resultsDisjunctiveFacets.aggregateFacets()
173+ val facetStats = results.aggregateFacetStats()
174+ val hierarchicalFacets = resultHierarchicalFacets.aggregateFacets()
175+
176+ return results.first().copy(
177+ facetStatsOrNull = if (facetStats.isEmpty()) null else facetStats,
178+ disjunctiveFacetsOrNull = facets,
179+ hierarchicalFacetsOrNull = if (hierarchicalFacets.isEmpty()) null else hierarchicalFacets,
180+ exhaustiveFacetsCountOrNull = resultsDisjunctiveFacets.all { it.exhaustiveFacetsCountOrNull == true }
181+ )
182+ }
183+
184+ private fun IndexQuery.optimize (): IndexQuery {
185+ query.apply {
186+ attributesToRetrieve = listOf ()
187+ attributesToHighlight = listOf ()
188+ hitsPerPage = 0
189+ analytics = false
190+ }
191+ return this
192+ }
193+
194+ private fun Query.toIndexQuery (): IndexQuery {
195+ return IndexQuery (indexName, copy())
196+ }
197+
198+ private fun IndexQuery.filters (
199+ filtersAnd : List <Filter >,
200+ filtersOrFacet : List <Filter .Facet >,
201+ filtersOrTag : List <Filter .Tag >,
202+ filtersOrNumeric : List <Filter .Numeric >
203+ ): IndexQuery {
204+ query.apply {
205+ filters {
206+ and { + filtersAnd }
207+ orFacet { + filtersOrFacet }
208+ orTag { + filtersOrTag }
209+ orNumeric { + filtersOrNumeric }
210+ }
211+ }
212+ return this
213+ }
214+
215+ private fun IndexQuery.setFacets (facet : Attribute ? ): IndexQuery {
216+ if (facet != null ) query.facets = setOf (facet)
217+ return this
218+ }
104219}
0 commit comments