Skip to content

Commit

Permalink
Merge Add MultiSearch and Fix CI/DC #14
Browse files Browse the repository at this point in the history
  • Loading branch information
programVeins authored Feb 12, 2022
2 parents d54544c + 5bc7dbb commit 815f639
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 13 deletions.
35 changes: 25 additions & 10 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1
# Use a package of configuration called an orb.
orbs:
# Declare a dependency on the welcome-orb
welcome: circleci/[email protected]
# Orchestrate or schedule a set of jobs

jobs:
test:
macos:
xcode: 13.2.1 # Using 13.2.1 to support backwards compatibility of Modern Concurrency
steps:
- checkout
- run:
name: Install Typesense
command: |
curl --output ts.tar.gz https://dl.typesense.org/releases/0.22.2/typesense-server-0.22.2-darwin-amd64.tar.gz
tar -xzf ts.tar.gz
- run:
name: Run Typesense
background: true
command: |
./typesense-server --api-key=xyz --data-dir=/tmp
- run:
# Build and Test the Typesense Package
name: Run Tests
command: swift test

workflows:
# Name the workflow "welcome"
welcome:
# Run the welcome/run job in its own container
version: 2
test_build:
jobs:
- welcome/run
- test
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.swiftpm
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "Typesense",
platforms: [
.iOS(.v13), .macOS(.v12)
.iOS(.v13), .macOS(.v10_15)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,5 @@ The generated Models (inside the Models directory) are to be used inside the Mod
## TODO: Features

- Curation API
- Multisearch
- Dealing with Dirty Data
- Scoped Search Key
4 changes: 4 additions & 0 deletions Sources/Typesense/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ public struct Client {
public func operations() -> Operations {
return Operations(config: self.configuration)
}

public func multiSearch() -> MultiSearch {
return MultiSearch(config: self.configuration)
}
}
177 changes: 177 additions & 0 deletions Sources/Typesense/MultiSearch.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import Foundation

public struct MultiSearch {
var apiCall: ApiCall
let RESOURCEPATH = "multi_search"

public init(config: Configuration) {
apiCall = ApiCall(config: config)
}

public func perform<T>(searchRequests: [MultiSearchCollectionParameters], commonParameters: MultiSearchParameters, for: T.Type) async throws -> (MultiSearchResult<T>?, URLResponse?) {
var searchQueryParams: [URLQueryItem] = []

if let query = commonParameters.q {
searchQueryParams.append(URLQueryItem(name: "q", value: query))
}

if let queryBy = commonParameters.queryBy {
searchQueryParams.append(URLQueryItem(name: "query_by", value: queryBy))
}

if let queryByWeights = commonParameters.queryByWeights {
searchQueryParams.append(URLQueryItem(name: "query_by_weights", value: queryByWeights))
}

if let maxHits = commonParameters.maxHits {
searchQueryParams.append(URLQueryItem(name: "max_hits", value: maxHits))
}

if let _prefix = commonParameters._prefix {
var fullString = ""
for item in _prefix {
fullString.append(String(item))
fullString.append(",")
}

searchQueryParams.append(URLQueryItem(name: "prefix", value: String(fullString.dropLast())))
}

if let filterBy = commonParameters.filterBy {
searchQueryParams.append(URLQueryItem(name: "filter_by", value: filterBy))
}

if let sortBy = commonParameters.sortBy {
searchQueryParams.append(URLQueryItem(name: "sort_by", value: sortBy))
}

if let facetBy = commonParameters.facetBy {
searchQueryParams.append(URLQueryItem(name: "facet_by", value: facetBy))
}

if let maxFacetValues = commonParameters.maxFacetValues {
searchQueryParams.append(URLQueryItem(name: "max_facet_values", value: String(maxFacetValues)))
}

if let facetQuery = commonParameters.facetQuery {
searchQueryParams.append(URLQueryItem(name: "facet_query", value: facetQuery))
}

if let numTypos = commonParameters.numTypos {
searchQueryParams.append(URLQueryItem(name: "num_typos", value: String(numTypos)))
}

if let page = commonParameters.page {
searchQueryParams.append(URLQueryItem(name: "page", value: String(page)))
}

if let perPage = commonParameters.perPage {
searchQueryParams.append(URLQueryItem(name: "per_page", value: String(perPage)))
}

if let groupBy = commonParameters.groupBy {
searchQueryParams.append(URLQueryItem(name: "group_by", value: groupBy))
}

if let groupLimit = commonParameters.groupLimit {
searchQueryParams.append(URLQueryItem(name: "group_limit", value: String(groupLimit)))
}

if let includeFields = commonParameters.includeFields {
searchQueryParams.append(URLQueryItem(name: "include_fields", value: includeFields))
}

if let excludeFields = commonParameters.excludeFields {
searchQueryParams.append(URLQueryItem(name: "exclude_fields", value: excludeFields))
}

if let highlightFullFields = commonParameters.highlightFullFields {
searchQueryParams.append(URLQueryItem(name: "highlight_full_fields", value: highlightFullFields))
}

if let highlightAffixNumTokens = commonParameters.highlightAffixNumTokens {
searchQueryParams.append(URLQueryItem(name: "highlight_affix_num_tokens", value: String(highlightAffixNumTokens)))
}

if let highlightStartTag = commonParameters.highlightStartTag {
searchQueryParams.append(URLQueryItem(name: "highlight_start_tag", value: highlightStartTag))
}

if let highlightEndTag = commonParameters.highlightEndTag {
searchQueryParams.append(URLQueryItem(name: "highlight_end_tag", value: highlightEndTag))
}

if let snippetThreshold = commonParameters.snippetThreshold {
searchQueryParams.append(URLQueryItem(name: "snippet_threshold", value: String(snippetThreshold)))
}

if let dropTokensThreshold = commonParameters.dropTokensThreshold {
searchQueryParams.append(URLQueryItem(name: "drop_tokens_threshold", value: String(dropTokensThreshold)))
}

if let typoTokensThreshold = commonParameters.typoTokensThreshold {
searchQueryParams.append(URLQueryItem(name: "typo_tokens_threshold", value: String(typoTokensThreshold)))
}

if let pinnedHits = commonParameters.pinnedHits {
searchQueryParams.append(URLQueryItem(name: "pinned_hits", value: pinnedHits))
}

if let hiddenHits = commonParameters.hiddenHits {
searchQueryParams.append(URLQueryItem(name: "hidden_hits", value: hiddenHits))
}

if let highlightFields = commonParameters.highlightFields {
searchQueryParams.append(URLQueryItem(name: "highlight_fields", value: highlightFields))
}

if let preSegmentedQuery = commonParameters.preSegmentedQuery {
searchQueryParams.append(URLQueryItem(name: "pre_segmented_query", value: String(preSegmentedQuery)))
}

if let enableOverrides = commonParameters.enableOverrides {
searchQueryParams.append(URLQueryItem(name: "enable_overrides", value: String(enableOverrides)))
}

if let prioritizeExactMatch = commonParameters.prioritizeExactMatch {
searchQueryParams.append(URLQueryItem(name: "prioritize_exact_match", value: String(prioritizeExactMatch)))
}

if let exhaustiveSearch = commonParameters.exhaustiveSearch {
searchQueryParams.append(URLQueryItem(name: "exhaustive_search", value: String(exhaustiveSearch)))
}

if let searchCutoffMs = commonParameters.searchCutoffMs {
searchQueryParams.append(URLQueryItem(name: "search_cutoff_ms", value: String(searchCutoffMs)))
}

if let useCache = commonParameters.useCache {
searchQueryParams.append(URLQueryItem(name: "use_cache", value: String(useCache)))
}

if let cacheTtl = commonParameters.cacheTtl {
searchQueryParams.append(URLQueryItem(name: "cache_ttl", value: String(cacheTtl)))
}

if let minLen1typo = commonParameters.minLen1typo {
searchQueryParams.append(URLQueryItem(name: "min_len1type", value: String(minLen1typo)))
}

if let minLen2typo = commonParameters.minLen2typo {
searchQueryParams.append(URLQueryItem(name: "min_len2type", value: String(minLen2typo)))
}

let searches = MultiSearchSearchesParameter(searches: searchRequests)

let searchesData = try encoder.encode(searches)

let (data, response) = try await apiCall.post(endPoint: "\(RESOURCEPATH)", body: searchesData, queryParameters: searchQueryParams)

if let validData = data {
let searchRes = try decoder.decode(MultiSearchResult<T>.self, from: validData)
return (searchRes, response)
}

return (nil, response)
}
}
90 changes: 90 additions & 0 deletions Tests/TypesenseTests/MultiSearchTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import XCTest
@testable import Typesense

final class MultiSearchTests: XCTestCase {

struct Product: Codable, Equatable {
var name: String?
var price: Int?
var brand: String?
var desc: String?

static func == (lhs: Product, rhs: Product) -> Bool {
return
lhs.name == rhs.name &&
lhs.price == rhs.price &&
lhs.brand == rhs.brand &&
lhs.desc == rhs.desc
}
}

struct Brand: Codable {
var name: String
}


func testMultiSearch() async {
let config = Configuration(nodes: [Node(host: "localhost", port: "8108", nodeProtocol: "http")], apiKey: "xyz", logger: Logger(debugMode: true))

let client = Client(config: config)

let productSchema = CollectionSchema(name: "products", fields: [
Field(name: "name", type: "string"),
Field(name: "price", type: "int32"),
Field(name: "brand", type: "string"),
Field(name: "desc", type: "string"),
])

let brandSchema = CollectionSchema(name: "brands", fields: [
Field(name: "name", type: "string"),
])

let searchRequests = [
MultiSearchCollectionParameters(q: "shoe", filterBy: "price:=[50..120]", collection: "products"),
MultiSearchCollectionParameters(q: "Nike", collection: "brands"),
]

let brand1 = Brand(name: "Nike")
let product1 = Product(name: "Jordan", price: 70, brand: "Nike", desc: "High quality shoe")

let commonParams = MultiSearchParameters(queryBy: "name")

do {
let (_, _) = try await client.collections.create(schema: productSchema) //Creating test collection - Products
let (_,_) = try await client.collections.create(schema: brandSchema)

let (_,_) = try await client.collection(name: "products").documents().create(document: encoder.encode(product1))

let (_,_) = try await client.collection(name: "brands").documents().create(document: encoder.encode(brand1))

let (data, _) = try await client.multiSearch().perform(searchRequests: searchRequests, commonParameters: commonParams, for: Product.self)

let (_,_) = try await client.collection(name: "products").delete() //Deleting test collection
let (_,_) = try await client.collection(name: "brands").delete() //Deleting test collection

XCTAssertNotNil(data)
guard let validResp = data else {
throw DataError.dataNotFound
}

XCTAssertNotNil(validResp.results)
XCTAssertNotEqual(validResp.results.count, 0)
XCTAssertNotNil(validResp.results[0].hits)
XCTAssertNotNil(validResp.results[1].hits)
XCTAssertEqual(validResp.results[1].hits?.count, 1)

print(validResp.results[1].hits as Any)
} catch HTTPError.serverError(let code, let desc) {
print(desc)
print("The response status code is \(code)")
XCTAssertTrue(false)
} catch (let error) {
print(error.localizedDescription)
XCTAssertTrue(false)
}

}



}

0 comments on commit 815f639

Please sign in to comment.