All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.algolia.client.extensions.internal.DisjunctiveFaceting.kt Maven / Gradle / Ivy

Go to download

"Algolia is a powerful search-as-a-service solution, made easy to use with API clients, UI libraries, and pre-built integrations. Algolia API Client for Kotlin lets you easily use the Algolia Search REST API from your JVM project, such as Android or backend implementations."

There is a newer version: 3.10.1
Show newest version
package com.algolia.client.extensions.internal

import com.algolia.client.extensions.SearchDisjunctiveFacetingResponse
import com.algolia.client.model.search.Exhaustive
import com.algolia.client.model.search.SearchForHits
import com.algolia.client.model.search.SearchResponse

/**
 * Helper making multiple queries for disjunctive faceting
 * and merging the multiple search responses into a single one with
 * combined facets information
 */
internal data class DisjunctiveFaceting(
  val query: SearchForHits,
  val refinements: Map>,
  val disjunctiveFacets: Set,
) {
  // Build filters SQL string from the provided refinements and disjunctive facets set
  internal fun buildFilters(excludedAttribute: String?): String {
    val filters = refinements.entries.sortedBy { it.key }.filter {
      it.key != excludedAttribute && it.value.isNotEmpty()
    }.map {
      val facetOperator = if (disjunctiveFacets.contains(it.key)) " OR " else " AND "
      val expression = it.value.joinToString(facetOperator) { value -> """"${it.key}":"$value"""" }
      return@map "($expression)"
    }
    return filters.joinToString(" AND ")
  }

  /*
   * Build search queries to fetch the necessary facets information for disjunctive faceting
   * If the disjunctive facets set is empty, makes a single request with applied conjunctive filters
   */
  fun buildQueries(): List {
    val queries = mutableListOf()

    val mainQueryFilters = listOf(
      query.filters,
      buildFilters(null),
    ).mapNotNull { it }
      .filter { it.isNotEmpty() }
      .joinToString(" AND ")

    queries.add(query.copy(filters = mainQueryFilters))

    disjunctiveFacets.sortedWith(compareBy { it }).forEach { facet ->
      val disjunctiveQuery = query.copy(
        facets = listOf(facet),
        filters = listOf(
          query.filters,
          buildFilters(facet),
        ).mapNotNull { it }
          .filter { it.isNotEmpty() }
          .joinToString(" AND "),
        hitsPerPage = 0,
        attributesToRetrieve = emptyList(),
        attributesToHighlight = emptyList(),
        attributesToSnippet = emptyList(),
        analytics = false,
      )

      queries.add(disjunctiveQuery)
    }

    return queries
  }

  // Get applied disjunctive facet values for provided attribute
  internal fun appliedDisjunctiveFacetValues(attribute: String): Set {
    if (!disjunctiveFacets.contains(attribute)) {
      return emptySet()
    }

    return refinements[attribute]?.toSet() ?: emptySet()
  }

  // Merge received search responses into single one with combined facets information
  fun mergeResponses(
    responses: List,
  ): SearchDisjunctiveFacetingResponse {
    val mainResponse = responses.first()
    val responsesForDisjunctiveFaceting = responses.drop(1)

    val mergedDisjunctiveFacets = mutableMapOf>()
    val mergedFacetStats = mainResponse.facetsStats?.toMutableMap() ?: mutableMapOf()
    var mergedExhaustiveFacetsCount = mainResponse.exhaustive?.facetsCount ?: false

    for (result in responsesForDisjunctiveFaceting) {
      // Merge facet values
      for ((attribute, facets) in result.facets ?: emptyMap()) {
        // Complete facet values applied in the filters
        // but missed in the search response
        val missingFacets = appliedDisjunctiveFacetValues(attribute).subtract(facets.keys).associateWith { 0 }
        mergedDisjunctiveFacets[attribute] = facets + missingFacets
      }

      // Merge facets stats
      mergedFacetStats.putAll(result.facetsStats ?: emptyMap())

      // If facet counts are not exhaustive, propagate this information to the main results.
      // Because disjunctive queries are less restrictive than the main query, it can happen that the main query
      // returns exhaustive facet counts, while the disjunctive queries do not.
      if (result.exhaustive?.facetsCount != null) {
        mergedExhaustiveFacetsCount = mergedExhaustiveFacetsCount && (result.exhaustive.facetsCount)
      }
    }

    return SearchDisjunctiveFacetingResponse(
      response = mainResponse.copy(
        facetsStats = mergedFacetStats,
        exhaustive = mainResponse.exhaustive?.copy(
          facetsCount = mergedExhaustiveFacetsCount,
        ) ?: Exhaustive(facetsCount = mergedExhaustiveFacetsCount),
      ),
      disjunctiveFacets = mergedDisjunctiveFacets,
    )
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy