Skip to content

Commit 698166f

Browse files
committed
Move advanced from Index to EndpointSearchImpl
1 parent 728b56a commit 698166f

File tree

3 files changed

+109
-170
lines changed

3 files changed

+109
-170
lines changed

src/commonMain/kotlin/com/algolia/search/client/Index.kt

Lines changed: 0 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -109,119 +109,4 @@ public data class Index internal constructor(
109109
}
110110
return responses
111111
}
112-
113-
suspend fun advancedSearch(
114-
query: Query = Query(),
115-
filterGroups: Set<FilterGroup<*>> = setOf(),
116-
requestOptions: RequestOptions? = null
117-
): ResponseSearch {
118-
val filtersAnd = filterGroups.filterIsInstance<FilterGroup.And<*>>().flatten()
119-
val filtersOr = filterGroups.filterIsInstance<FilterGroup.Or<*>>().flatten()
120-
val disjunctiveFacets = filtersOr.map { it.attribute }.toSet()
121-
val filtersOrFacet = filtersOr.filterIsInstance<Filter.Facet>()
122-
val filtersOrTag = filtersOr.filterIsInstance<Filter.Tag>()
123-
val filtersOrNumeric = filtersOr.filterIsInstance<Filter.Numeric>()
124-
val queryForResults = query
125-
.toIndexQuery()
126-
.filters(filtersAnd, filtersOrFacet, filtersOrTag, filtersOrNumeric)
127-
val queriesForDisjunctiveFacets = disjunctiveFacets.map { attribute ->
128-
query
129-
.toIndexQuery()
130-
.filters(
131-
filtersAnd,
132-
filtersOrFacet.filter { it.attribute != attribute },
133-
filtersOrTag,
134-
filtersOrNumeric
135-
)
136-
.setFacets(attribute)
137-
.optimize()
138-
}
139-
val queriesForHierarchicalFacets = filterGroups.filterIsInstance<FilterGroup.And.Hierarchical>().flatMap {
140-
it.attributes
141-
.take(it.path.size + 1)
142-
.mapIndexed { index, attribute ->
143-
query
144-
.toIndexQuery()
145-
.filters(
146-
filtersAnd.combine(it.path.getOrNull(index - 1)).minus(it.path.last()),
147-
filtersOrFacet,
148-
filtersOrTag,
149-
filtersOrNumeric
150-
)
151-
.setFacets(attribute)
152-
.optimize()
153-
}
154-
}
155-
val queries = listOf(queryForResults) + queriesForDisjunctiveFacets + queriesForHierarchicalFacets
156-
val response = EndpointMultipleIndexImpl(transport).multipleQueries(queries, requestOptions = requestOptions)
157-
158-
return response.aggregateResult(disjunctiveFacets.size)
159-
}
160-
161-
private fun List<ResponseSearch>.aggregateFacets(): Map<Attribute, List<Facet>> {
162-
return fold(mapOf()) { acc, result ->
163-
result.facetsOrNull?.let { acc + it } ?: acc
164-
}
165-
}
166-
167-
private fun List<ResponseSearch>.aggregateFacetStats(): Map<Attribute, FacetStats> {
168-
return fold(mapOf()) { acc, result ->
169-
result.facetStatsOrNull?.let { acc + it } ?: acc
170-
}
171-
}
172-
173-
private fun List<Filter>.combine(hierarchicalFilter: Filter.Facet?): List<Filter> {
174-
return hierarchicalFilter?.let { this + it } ?: this
175-
}
176-
177-
private fun ResponseSearches.aggregateResult(disjunctiveFacetCount: Int): ResponseSearch {
178-
val resultsDisjunctiveFacets = results.subList(1, 1 + disjunctiveFacetCount)
179-
val resultHierarchicalFacets = results.subList(1 + disjunctiveFacetCount, results.size)
180-
val facets = resultsDisjunctiveFacets.aggregateFacets()
181-
val facetStats = results.aggregateFacetStats()
182-
val hierarchicalFacets = resultHierarchicalFacets.aggregateFacets()
183-
184-
return results.first().copy(
185-
facetStatsOrNull = if (facetStats.isEmpty()) null else facetStats,
186-
disjunctiveFacetsOrNull = facets,
187-
hierarchicalFacetsOrNull = if (hierarchicalFacets.isEmpty()) null else hierarchicalFacets,
188-
exhaustiveFacetsCountOrNull = resultsDisjunctiveFacets.all { it.exhaustiveFacetsCountOrNull == true }
189-
)
190-
}
191-
192-
private fun IndexQuery.optimize(): IndexQuery {
193-
query.apply {
194-
attributesToRetrieve = listOf()
195-
attributesToHighlight = listOf()
196-
hitsPerPage = 0
197-
analytics = false
198-
}
199-
return this
200-
}
201-
202-
private fun Query.toIndexQuery(): IndexQuery {
203-
return IndexQuery(indexName, copy())
204-
}
205-
206-
private fun IndexQuery.filters(
207-
filtersAnd: List<Filter>,
208-
filtersOrFacet: List<Filter.Facet>,
209-
filtersOrTag: List<Filter.Tag>,
210-
filtersOrNumeric: List<Filter.Numeric>
211-
): IndexQuery {
212-
query.apply {
213-
filters {
214-
and { +filtersAnd }
215-
orFacet { +filtersOrFacet }
216-
orTag { +filtersOrTag }
217-
orNumeric { +filtersOrNumeric }
218-
}
219-
}
220-
return this
221-
}
222-
223-
private fun IndexQuery.setFacets(facet: Attribute?): IndexQuery {
224-
if (facet != null) query.facets = setOf(facet)
225-
return this
226-
}
227112
}

src/commonMain/kotlin/com/algolia/search/endpoint/EndpointSearch.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.algolia.search.endpoint
33
import com.algolia.search.model.Attribute
44
import com.algolia.search.model.IndexName
55
import com.algolia.search.model.filter.Filter
6+
import com.algolia.search.model.filter.FilterGroup
67
import com.algolia.search.model.response.ResponseSearch
78
import com.algolia.search.model.response.ResponseSearchForFacets
89
import com.algolia.search.model.search.Cursor
@@ -108,17 +109,16 @@ public interface EndpointSearch {
108109
/**
109110
* Perform a [EndpointMultipleIndex.multipleQueries] and aggregate the results into a single [ResponseSearch].
110111
* The aggregated result will have a correct [com.algolia.search.model.search.Facet.count] for [Attribute] that are
111-
* marked as disjunctive.
112+
* marked as disjunctive, and will populate [ResponseSearch.hierarchicalFacets] if a [FilterGroup.And.Hierarchical]
113+
* is present in [filterGroups]
112114
*
113115
* @param query The [Query] used to search.
114-
* @param disjunctiveFacets List of [Attribute] that are marked as disjunctive facets.
115-
* @param filters The [Filter] to be applied.
116+
* @param filterGroups List of [FilterGroup] to apply to the query.
116117
* @param requestOptions Configure request locally with [RequestOptions].
117118
*/
118-
suspend fun searchDisjunctiveFacets(
119-
query: Query,
120-
disjunctiveFacets: List<Attribute>,
121-
filters: Set<Filter>,
119+
suspend fun advancedSearch(
120+
query: Query = Query(),
121+
filterGroups: Set<FilterGroup<*>> = setOf(),
122122
requestOptions: RequestOptions? = null
123123
): ResponseSearch
124124
}

src/commonMain/kotlin/com/algolia/search/endpoint/EndpointSearchImpl.kt

Lines changed: 102 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import com.algolia.search.dsl.requestOptionsBuilder
66
import com.algolia.search.model.Attribute
77
import com.algolia.search.model.IndexName
88
import com.algolia.search.model.filter.Filter
9+
import com.algolia.search.model.filter.FilterGroup
910
import com.algolia.search.model.multipleindex.IndexQuery
1011
import com.algolia.search.model.request.RequestParams
1112
import com.algolia.search.model.response.ResponseSearch
1213
import com.algolia.search.model.response.ResponseSearchForFacets
14+
import com.algolia.search.model.response.ResponseSearches
1315
import com.algolia.search.model.search.Cursor
16+
import com.algolia.search.model.search.Facet
1417
import com.algolia.search.model.search.FacetStats
1518
import com.algolia.search.model.search.Query
1619
import com.algolia.search.serialize.*
@@ -61,67 +64,118 @@ internal class EndpointSearchImpl(
6164
return transport.request(HttpMethod.Post, CallType.Read, path, requestOptions, body)
6265
}
6366

64-
override suspend fun searchDisjunctiveFacets(
67+
override suspend fun advancedSearch(
6568
query: Query,
66-
disjunctiveFacets: List<Attribute>,
67-
filters: Set<Filter>,
69+
filterGroups: Set<FilterGroup<*>>,
6870
requestOptions: RequestOptions?
6971
): ResponseSearch {
70-
val (filtersOr, filtersAnd) = filters.partition { disjunctiveFacets.contains(it.attribute) }
71-
val queryAnd = buildAndQueries(query, filtersAnd, filtersOr)
72-
val queriesOr = buildOrQueries(query, filtersAnd, filtersOr, disjunctiveFacets)
73-
val results = EndpointMultipleIndexImpl(transport).multipleQueries(queryAnd.plus(queriesOr)).results
74-
val resultAnd = results.first()
75-
val resultsOr = results.subList(1, results.size)
76-
val facets = resultsOr.map { it.facets.toMutableMap() }.reduce { acc, map -> acc.apply { this += map } }
77-
val facetStats = mutableMapOf<Attribute, FacetStats>()
78-
79-
resultAnd.facetStatsOrNull?.let { facetStats += it }
80-
resultsOr.forEach { result ->
81-
result.facetStatsOrNull?.let { facetStats += it }
72+
val filtersAnd = filterGroups.filterIsInstance<FilterGroup.And<*>>().flatten()
73+
val filtersOr = filterGroups.filterIsInstance<FilterGroup.Or<*>>().flatten()
74+
val disjunctiveFacets = filtersOr.map { it.attribute }.toSet()
75+
val filtersOrFacet = filtersOr.filterIsInstance<Filter.Facet>()
76+
val filtersOrTag = filtersOr.filterIsInstance<Filter.Tag>()
77+
val filtersOrNumeric = filtersOr.filterIsInstance<Filter.Numeric>()
78+
val queryForResults = query
79+
.toIndexQuery()
80+
.filters(filtersAnd, filtersOrFacet, filtersOrTag, filtersOrNumeric)
81+
val queriesForDisjunctiveFacets = disjunctiveFacets.map { attribute ->
82+
query
83+
.toIndexQuery()
84+
.filters(
85+
filtersAnd,
86+
filtersOrFacet.filter { it.attribute != attribute },
87+
filtersOrTag,
88+
filtersOrNumeric
89+
)
90+
.setFacets(attribute)
91+
.optimize()
8292
}
83-
return resultAnd.copy(
93+
val queriesForHierarchicalFacets = filterGroups.filterIsInstance<FilterGroup.And.Hierarchical>().flatMap {
94+
it.attributes
95+
.take(it.path.size + 1)
96+
.mapIndexed { index, attribute ->
97+
query
98+
.toIndexQuery()
99+
.filters(
100+
filtersAnd.combine(it.path.getOrNull(index - 1)).minus(it.path.last()),
101+
filtersOrFacet,
102+
filtersOrTag,
103+
filtersOrNumeric
104+
)
105+
.setFacets(attribute)
106+
.optimize()
107+
}
108+
}
109+
val queries = listOf(queryForResults) + queriesForDisjunctiveFacets + queriesForHierarchicalFacets
110+
val response = EndpointMultipleIndexImpl(transport).multipleQueries(queries, requestOptions = requestOptions)
111+
112+
return response.aggregateResult(disjunctiveFacets.size)
113+
}
114+
115+
private fun List<ResponseSearch>.aggregateFacets(): Map<Attribute, List<Facet>> {
116+
return fold(mapOf()) { acc, result ->
117+
result.facetsOrNull?.let { acc + it } ?: acc
118+
}
119+
}
120+
121+
private fun List<ResponseSearch>.aggregateFacetStats(): Map<Attribute, FacetStats> {
122+
return fold(mapOf()) { acc, result ->
123+
result.facetStatsOrNull?.let { acc + it } ?: acc
124+
}
125+
}
126+
127+
private fun List<Filter>.combine(hierarchicalFilter: Filter.Facet?): List<Filter> {
128+
return hierarchicalFilter?.let { this + it } ?: this
129+
}
130+
131+
private fun ResponseSearches.aggregateResult(disjunctiveFacetCount: Int): ResponseSearch {
132+
val resultsDisjunctiveFacets = results.subList(1, 1 + disjunctiveFacetCount)
133+
val resultHierarchicalFacets = results.subList(1 + disjunctiveFacetCount, results.size)
134+
val facets = resultsDisjunctiveFacets.aggregateFacets()
135+
val facetStats = results.aggregateFacetStats()
136+
val hierarchicalFacets = resultHierarchicalFacets.aggregateFacets()
137+
138+
return results.first().copy(
139+
facetStatsOrNull = if (facetStats.isEmpty()) null else facetStats,
84140
disjunctiveFacetsOrNull = facets,
85-
exhaustiveFacetsCountOrNull = resultsOr.any { it.exhaustiveFacetsCountOrNull == true },
86-
facetStatsOrNull = if (facetStats.isEmpty()) null else facetStats
141+
hierarchicalFacetsOrNull = if (hierarchicalFacets.isEmpty()) null else hierarchicalFacets,
142+
exhaustiveFacetsCountOrNull = resultsDisjunctiveFacets.all { it.exhaustiveFacetsCountOrNull == true }
87143
)
88144
}
89145

90-
private fun buildAndQueries(
91-
query: Query,
146+
private fun IndexQuery.optimize(): IndexQuery {
147+
query.apply {
148+
attributesToRetrieve = listOf()
149+
attributesToHighlight = listOf()
150+
hitsPerPage = 0
151+
analytics = false
152+
}
153+
return this
154+
}
155+
156+
private fun Query.toIndexQuery(): IndexQuery {
157+
return IndexQuery(indexName, copy())
158+
}
159+
160+
private fun IndexQuery.filters(
92161
filtersAnd: List<Filter>,
93-
filtersOr: List<Filter>
94-
): List<IndexQuery> {
95-
return query.copy().apply {
162+
filtersOrFacet: List<Filter.Facet>,
163+
filtersOrTag: List<Filter.Tag>,
164+
filtersOrNumeric: List<Filter.Numeric>
165+
): IndexQuery {
166+
query.apply {
96167
filters {
97168
and { +filtersAnd }
98-
orFacet { +filtersOr.filterIsInstance<Filter.Facet>() }
99-
orTag { +filtersOr.filterIsInstance<Filter.Tag>() }
100-
orNumeric { +filtersOr.filterIsInstance<Filter.Numeric>() }
169+
orFacet { +filtersOrFacet }
170+
orTag { +filtersOrTag }
171+
orNumeric { +filtersOrNumeric }
101172
}
102-
}.let { listOf(IndexQuery(indexName, it)) }
173+
}
174+
return this
103175
}
104176

105-
private fun buildOrQueries(
106-
query: Query,
107-
filtersAnd: List<Filter>,
108-
filtersOr: List<Filter>,
109-
disjunctiveFacets: List<Attribute>
110-
): List<IndexQuery> {
111-
return disjunctiveFacets.map { attribute ->
112-
query.copy().apply {
113-
facets = setOf(attribute)
114-
attributesToRetrieve = listOf()
115-
attributesToHighlight = listOf()
116-
hitsPerPage = 0
117-
analytics = false
118-
filters {
119-
and { +filtersAnd }
120-
orFacet { +filtersOr.filterIsInstance<Filter.Facet>().filter { it.attribute != attribute } }
121-
orTag { +filtersOr.filterIsInstance<Filter.Tag>() }
122-
orNumeric { +filtersOr.filterIsInstance<Filter.Numeric>() }
123-
}
124-
}
125-
}.map { IndexQuery(indexName, it) }
177+
private fun IndexQuery.setFacets(facet: Attribute?): IndexQuery {
178+
if (facet != null) query.facets = setOf(facet)
179+
return this
126180
}
127181
}

0 commit comments

Comments
 (0)