algolia / instantsearch-core-swift

⚡️ InstantSearch Core library for Swift and Objective-C

Home Page:https://community.algolia.com/instantsearch-core-swift/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Disjunctive Facets are Complicated

zgosalvez opened this issue · comments

Simple Filter String

Using the API directly, I can create a filter like so "objectID: AAA OR objectID: BBB"

Algolia request log

{
  "params": "filters=(%22objectID%22:%22AAA%22%20OR%20%22objectID%22:%22BBB%22)"
}

Algolia response
A single result object with expected hits.

Complex Refinements

Using the library, it creates unnecessary multiple requests objects that don't return the same as the API like so

searcher.params.setFacet(withName: "objectID", disjunctive: true)
searcher.params.addFacetRefinement(name: "objectID", value: 1)
searcher.params.addFacetRefinement(name: "objectID", value: 2)

Algolia request log

{
  "requests": [
    {
      "params": "facetFilters=[[%22objectID:AAA%22,%22objectID:BBB%22]]",
      "indexName": "objects"
    },
    {
      "params": "analytics=false&attributesToHighlight=[]&attributesToRetrieve=[]&attributesToSnippet=[]&facetFilters=[]&facets=[%22objectID%22]",
      "indexName": "objects"
    }
  ]
}

Algolia response
An array of results that have unexpected hits.


After going through some of the source files, I found that a section of related code at Searcher.swift:417 has not been updated for 2 years! More importantly, I don't understand why it has to go through multiple requests objects if SearchParameter.swift@buildFiltersFromFacets was created at the same time, and it also provides the logic for generating simple " OR " filters. I can't identify a code path that would lead it to use this simpler logic.

I agree with this still open issue that there should be a way for us to manually set our own filters, especially with more complex use cases.

For now, I settled with a workaround by modifying Searcher.swift@startRequest. Note: This may not work for everyone, but it works for all of my use cases.

From

var operation: Operation
if state.disjunctiveFacets.isEmpty {
    // All facets are conjunctive; build regular filters combining numeric and facet refinements.
    // NOTE: Not strictly necessary since `Index.search(...)` calls `Query.build()`, but let's not rely on that.
    params.update()
    operation = index.search(params, requestOptions: nil, completionHandler: completionHandler)
} else {
    // Facet filters are built directly by the disjunctive faceting search helper method.
    params.updateFromNumerics() // this is really necessary (in contrast to the above)
    let refinements = params.buildFacetRefinements()
    operation = index.searchDisjunctiveFaceting(params, disjunctiveFacets: state.disjunctiveFacets, refinements: refinements, requestOptions: nil, completionHandler: completionHandler)
}

To

params.update()
operation = index.search(params, requestOptions: nil, completionHandler: completionHandler)

Hi @zgosalvez ,

Sorry for the late reply on this!

The reason why we do more than 1 request is described in details here. To give a quick summary, we do it because we need information about the number of facet counts for the disjunctive facet(s) in order to have the necessary information to build a refinement list showing how many hits would be added to the result set if it is selected.

So I guess what you're trying to say is that you don't need to display a refinement list, and so you don't want more than 1 request to happen, is that correct? If that's the case, then I feel it is a valid point that we need to address, maybe we should put it as a setting that you can turn on or off. I just opened up the discussion with people working on our API Clients (since this has been the default behaviour in our other API clients). I will keep this issue open for now until we resolve it.

Your workaround is basically removing all those extra calls to build the disjunctive refinement lists, which is what that "flag" or "setting" would do. So you can go ahead with that as it works for your use case.

Thank you for the explanation, @spinach. You are correct, I just want 1 request to happen, so I'm sticking with the workaround for now.

I think the simplest approach is to allow us to manually build the query - as easy as using the AlgoliaSearch API - while getting the benefits of using InstantSearch. It's basically just a JSON object.

Yep definitely good feedback, thanks again for this, we're currently discussing how to modify our API clients to fit those use cases.

The new version of InstantSearch can help with this issue. You can decide to turn off "multiple requests" even in the disjunctive case with this property found in the SingleIndexSearcher

  /// Flag defining if disjunctive faceting is enabled
  /// - Default value: true
  public var isDisjunctiveFacetingEnabled = true