Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perform multiple searches in one single HTTP request #379

Open
2 tasks
brunoocasali opened this issue Apr 27, 2023 · 6 comments
Open
2 tasks

Perform multiple searches in one single HTTP request #379

brunoocasali opened this issue Apr 27, 2023 · 6 comments
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@brunoocasali
Copy link
Member

brunoocasali commented Apr 27, 2023

⚠️ This issue is generated, it means the examples and the namings do not necessarily correspond to the language of this repository.
Also, if you are a maintainer, feel free to add any clarification and instruction about this issue.

Sorry if this is already partially/completely implemented, feel free to let me know about the state of this issue in the repo.

Related to meilisearch/integration-guides#251


Related to:

Create a new client method called multiSearch/multi_search, which will request POST /multi-search with a body payload containing a structure similar to this:

{
   "queries": [ 
      { "indexUid": "movie", "q": "wonder", "limit": 10 }, 
      { "indexUid": "books", "q": "king", "page": 2 } 
    ]
}

Each object is a simple search object sent in the POST /indexes/:indexUid/search request.

Pay attention to the response, which will follow the order of the requests object and will look like this (note the new indexUid key in the response):

{
   "results": [ 
      {
         "indexUid": "movie",
         "hits": [ { "title": "wonderwoman" } ],
         // other search results fields: processingTimeMs, limit, ...
      },
      {
         "indexUid": "books",
         "hits": [ { "title": "king kong theory" } ],
         // other search results fields: processingTimeMs, limit, ...
      },
   ]
}

TODO:

  • Add the new multiSearch method
  • Add tests
@brunoocasali brunoocasali added enhancement New feature or request good first issue Good for newcomers labels Apr 27, 2023
@Sherlouk
Copy link
Collaborator

Sherlouk commented Nov 4, 2023

@brunoocasali how do other compiler safe languages handle this API?

It's proving exceptionally difficult to have a method which expects an array of generics with different document types.

The best I've got (code below) is using message packs which syntaxically to the user looks nice but I'm unable to figure out how to zip up the pack of expected document types to an array of data from the API. I think this is because the feature is so new in Swift it lacks things like message pack iteration.

Work in Progress

  @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  func multiSearch(
    _ searchParameters: [MultiSearchParameters]
  ) async throws -> (repeat Searchable) {
let data: Data
do {
  var jsonBuilder = [[String: Any]]()
  
  for searchParameter in searchParameters {
    let jsonData = try JSONEncoder().encode(searchParameter.parameters)
    
    var jsonObject = try JSONSerialization.jsonObject(with: jsonData) as! [String: Any]
    jsonObject["indexUid"] = searchParameter.indexUid
    
    jsonBuilder.append(jsonObject)
  }
  
  data = try JSONSerialization.data(withJSONObject: [ "queries": jsonBuilder ])
} catch {
  throw MeiliSearch.Error.invalidJSON
}

return try await withCheckedThrowingContinuation { continuation in
  self.request.post(api: "/multi-search", data) { result in
    switch result {
    case .success(let data):
      
      do {
        let resultHolder = try JSONSerialization.jsonObject(with: data) as! [String: [Any]]
        
        let results: [Data] = try resultHolder["results"]!.map {
          try JSONSerialization.data(withJSONObject: $0)
        }
        
        print(results) // An array of JSON objects in Data form, ready for decoding.
        
        // ... magic?
      } catch {
        continuation.resume(throwing: error)
      }
      
    case .failure(let failure):
      continuation.resume(throwing: failure)
    }
  }
}

}

Usage:

let params: [MultiSearchParameters] = [
  .init(indexUid: "indexA", parameters: .query("b")),
  .init(indexUid: "indexB", parameters: .init(query: "b", limit: 5))
]

let results: (Searchable<Movie>, Searchable<Animal>) = try await search.multiSearch(params)
print(results.0.hits.first?.document.id)

The simple solution would be to return an array of undecoded types. So the usage would look like:

let params: [MultiSearchParameters] = [
  .init(indexUid: "indexA", parameters: .query("b")),
  .init(indexUid: "indexB", parameters: .init(query: "b", limit: 5))
]

let results = try await search.multiSearch(params)
// notice the new `.decode()` function to move from the undecoded type to something they can use. 
// this helps the compiler to know what the type is though simplifying the solution.
let firstResult: Searchable<Movie> = results[0].decode()
let secondResult: Searchable<Animal> = results[1].decode()

This feels like an okay compromise. The API is still pretty clean and clear what's happening.

It does lose the safety though where a user could do results[2].decode() and crash due to there not being that many searches. The first solution wouldn't allow this.

@brunoocasali
Copy link
Member Author

brunoocasali commented Dec 15, 2023

This will be an issue to be accomplished here indeed.

I guess maybe you could take inspiration of those SDKS: Java (proposal), Rust and C#

Does it helps?

@Sherlouk
Copy link
Collaborator

Unfortunately not, none of the three solutions above appear to demonstrate a compiler-safe and type-safe solution which allows for queries to different indexes with different document types.

The Rust one for example seems to show that all documents across the multiple queries must be of type Movie.

This would be an easy limitation for us to implement, but one which dramatically reduces the usefulness.

Separating the decode step (my approach above) adds one extra step to the execution but allows for mixed result responses. Think it's what we need to go for, for now, until more language capabilities are added (message packs)

@brunoocasali
Copy link
Member Author

brunoocasali commented Dec 15, 2023

Separating the decode step (my approach above) adds one extra step to the execution but allows for mixed result responses.

I guess it is the way to go in this case @Sherlouk 😢.

@JOyo246
Copy link

JOyo246 commented Oct 4, 2024

just to double check, multiSearch is not available in the Swift SDK?

@Sherlouk
Copy link
Collaborator

Sherlouk commented Oct 5, 2024

Not at this time, we've yet to identify a completely compile/type safe solution which works in Swift.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

3 participants