Skip to content

Commit 9a0e3fa

Browse files
authored
Merge pull request #107 from algolia/feature/hierarchical
Feature/hierarchical
2 parents ef33663 + 27449b1 commit 9a0e3fa

File tree

10 files changed

+517
-7
lines changed

10 files changed

+517
-7
lines changed

buildSrc/src/main/kotlin/Library.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ object Library: Dependency {
22

33
override val group = "com.algolia"
44
override val artifact = "algoliasearch-client-kotlin"
5-
override val version = "1.1.0"
5+
override val version = "1.1.1"
66
}

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package com.algolia.search.client
22

3+
import com.algolia.search.dsl.filters
34
import com.algolia.search.endpoint.*
5+
import com.algolia.search.model.Attribute
46
import com.algolia.search.model.IndexName
7+
import com.algolia.search.model.filter.Filter
8+
import com.algolia.search.model.multipleindex.IndexQuery
59
import com.algolia.search.model.response.ResponseSearch
610
import com.algolia.search.model.response.ResponseSearchRules
711
import com.algolia.search.model.response.ResponseSearchSynonyms
12+
import com.algolia.search.model.response.ResponseSearches
813
import com.algolia.search.model.rule.Rule
914
import com.algolia.search.model.rule.RuleQuery
15+
import com.algolia.search.model.search.Facet
16+
import com.algolia.search.model.search.FacetStats
1017
import com.algolia.search.model.search.Query
1118
import com.algolia.search.model.synonym.Synonym
1219
import 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
}

src/commonMain/kotlin/com/algolia/search/configuration/ConfigurationSearch.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public data class ConfigurationSearch(
2222
override val defaultHeaders: Map<String, String>? = null,
2323
override val engine: HttpClientEngine? = null,
2424
override val httpClientConfig: (HttpClientConfig<*>.() -> Unit)? = null,
25-
override val compression: Compression = Compression.Gzip
25+
override val compression: Compression = Compression.None
2626
) : Configuration, Credentials {
2727

2828
override val httpClient = getHttpClient()

src/commonMain/kotlin/com/algolia/search/model/request/RequestMultipleQueries.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class RequestMultipleQueries(
2323
obj.indexQueries.forEach {
2424
+json {
2525
KeyIndexName to it.indexName.raw
26-
KeyParams to it.query.toJsonNoDefaults().urlEncode()
26+
it.query.toJsonNoDefaults().urlEncode()?.let { KeyParams to it }
2727
}
2828
}
2929
}

src/commonMain/kotlin/com/algolia/search/model/response/ResponseSearch.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,11 @@ public data class ResponseSearch(
149149
/**
150150
* Identifies the query uniquely. Can be used by [InsightsEvent].
151151
*/
152-
@SerialName(KeyQueryID) val queryIDOrNull: QueryID? = null
152+
@SerialName(KeyQueryID) val queryIDOrNull: QueryID? = null,
153+
/**
154+
*
155+
*/
156+
@SerialName(KeyHierarchicalFacets) val hierarchicalFacetsOrNull: Map<Attribute, List<Facet>>? = null
153157
) {
154158

155159
@Transient
@@ -264,6 +268,10 @@ public data class ResponseSearch(
264268
public val queryID: QueryID
265269
get() = queryIDOrNull!!
266270

271+
@Transient
272+
public val hierarchicalFacets: Map<Attribute, List<Facet>>
273+
get() = hierarchicalFacetsOrNull!!
274+
267275
/**
268276
* A Hit returned by the search.
269277
*/

src/commonMain/kotlin/com/algolia/search/serialize/Keys.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ public const val KeyExactPhrase = "exactPhrase"
330330
public const val KeyExcludeWords = "excludeWords"
331331
public const val KeyAdvancedSyntaxFeatures = "advancedSyntaxFeatures"
332332
public const val KeyPersonalizationImpact = "personalizationImpact"
333+
public const val KeyHierarchicalFacets = "hierarchicalFacets"
333334
public const val KeyLanguage = "language"
334335
public const val KeyCountries = "countries"
335336
public const val KeyCity = "city"

src/commonMain/kotlin/com/algolia/search/transport/Transport.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ internal class Transport(
8080
requestBuilder.url.host = host.url
8181
try {
8282
return withTimeout((host.retryCount + 1) * timeout) {
83-
val response = httpClient.request<T>(requestBuilder)
84-
mutex.withLock { host.reset() }
85-
response
83+
httpClient.request<T>(requestBuilder).apply {
84+
mutex.withLock { host.reset() }
85+
}
8686
}
8787
} catch (exception: TimeoutCancellationException) {
8888
mutex.withLock { host.hasTimedOut() }

0 commit comments

Comments
 (0)