R delete(String endpoint, Q queryParameters, ClassresponseClass) thro } T makeRequest(String endpoint, Q queryParameters, Request.Builder requestBuilder, - ClassresponseClass) throws Exception { + Class responseClass) throws Exception { int num_tries = 0; Exception lastException = new TypesenseError("Unknown client error", 400); @@ -174,9 +172,9 @@ T makeRequest(String endpoint, Q queryParameters, Request.Builder request String url = URI + endpoint; String fullUrl = populateQueryParameters(url, queryParameters).toString(); Request request = requestBuilder - .url(fullUrl) - .header(API_KEY_HEADER, apiKey) - .build(); + .url(fullUrl) + .header(API_KEY_HEADER, apiKey) + .build(); Response response = client.newCall(request).execute(); @@ -193,11 +191,11 @@T makeRequest(String endpoint, Q queryParameters, Request.Builder request } catch (Exception e) { boolean handleError = (e instanceof ServerError) || - (e instanceof ServiceUnavailable) || - (e.getClass().getPackage().getName().startsWith("java.net")) || - (e instanceof SSLException); + (e instanceof ServiceUnavailable) || + (e.getClass().getPackage().getName().startsWith("java.net")) || + (e instanceof SSLException); - if(!handleError) { + if (!handleError) { // we just throw and move on throw e; } @@ -233,7 +231,7 @@ privateHttpUrl.Builder populateQueryParameters(String url, T queryParameter value.append(","); } httpBuilder.addQueryParameter(entry.getKey(), value.toString()); - } else if (entry.getValue() != null){ + } else if (entry.getValue() != null) { httpBuilder.addQueryParameter(entry.getKey(), entry.getValue().toString()); } } @@ -272,3 +270,10 @@ T handleResponse(Response response, Class responseClass) throws IOExcepti } +class ErrorResponse { + private String message = null; + + public String getMessage() { + return message; + } +} \ No newline at end of file diff --git a/src/main/java/org/typesense/api/Client.java b/src/main/java/org/typesense/api/Client.java index 6f69a60..b892628 100644 --- a/src/main/java/org/typesense/api/Client.java +++ b/src/main/java/org/typesense/api/Client.java @@ -1,5 +1,8 @@ package org.typesense.api; + +import okhttp3.OkHttpClient; + import java.util.HashMap; import java.util.Map; @@ -17,12 +20,46 @@ public class Client { private Keys keys; private Map individualKeys; + + private Analytics analytics; + + private Stemming stemming; + + private Stopwords stopwords; + private Map individualStopwordsSets; + + private SynonymSets synonymSets; + private Map individualSynonymSets; + + private CurationSets curationSets; + private Map individualCurationSets; + public Health health; public Operations operations; public Metrics metrics; public Debug debug; public MultiSearch multiSearch; + public Client(Configuration configuration, OkHttpClient okHttpClient){ + this.configuration = configuration; + this.apiCall = new ApiCall(configuration, okHttpClient); + collections = new Collections(apiCall); + this.individualCollections = new HashMap<>(); + this.aliases = new Aliases(this.apiCall); + this.individualAliases = new HashMap<>(); + this.keys = new Keys(this.apiCall); + this.individualKeys = new HashMap<>(); + this.health = new Health(this.apiCall); + this.operations = new Operations(this.apiCall); + this.metrics = new Metrics(this.apiCall); + this.debug = new Debug(this.apiCall); + this.multiSearch = new MultiSearch(this.apiCall); + this.analytics = new Analytics(this.apiCall); + this.stemming = new Stemming(this.apiCall); + this.stopwords = new Stopwords(this.apiCall); + this.individualStopwordsSets = new HashMap<>(); + } + public Client(Configuration configuration){ this.configuration = configuration; this.apiCall = new ApiCall(configuration); @@ -37,6 +74,14 @@ public Client(Configuration configuration){ this.metrics = new Metrics(this.apiCall); this.debug = new Debug(this.apiCall); this.multiSearch = new MultiSearch(this.apiCall); + this.analytics = new Analytics(this.apiCall); + this.stemming = new Stemming(this.apiCall); + this.stopwords = new Stopwords(this.apiCall); + this.individualStopwordsSets = new HashMap<>(); + this.synonymSets = new SynonymSets(this.apiCall); + this.individualSynonymSets = new HashMap<>(); + this.curationSets = new CurationSets(this.apiCall); + this.individualCurationSets = new HashMap<>(); } public Collection collections(String name){ @@ -85,4 +130,57 @@ public Key keys(Long id){ retVal = this.individualKeys.get(id); return retVal; } + + public Analytics analytics(){ + return this.analytics; + } + + public Stemming stemming(){ + return this.stemming; + } + + public Stopwords stopwords() { + return this.stopwords; + } + + public StopwordsSet stopwords(String stopwordsSetId) { + StopwordsSet retVal; + + if (!this.individualStopwordsSets.containsKey(stopwordsSetId)) { + this.individualStopwordsSets.put(stopwordsSetId, new StopwordsSet(stopwordsSetId, this.apiCall)); + } + + retVal = this.individualStopwordsSets.get(stopwordsSetId); + return retVal; + } + + public SynonymSets synonymSets() { + return this.synonymSets; + } + + public SynonymSet synonymSet(String synonymSetName) { + SynonymSet retVal; + + if (!this.individualSynonymSets.containsKey(synonymSetName)) { + this.individualSynonymSets.put(synonymSetName, new SynonymSet(synonymSetName, this.apiCall)); + } + + retVal = this.individualSynonymSets.get(synonymSetName); + return retVal; + } + + public CurationSets curationSets() { + return this.curationSets; + } + + public CurationSet curationSet(String curationSetName) { + CurationSet retVal; + + if (!this.individualCurationSets.containsKey(curationSetName)) { + this.individualCurationSets.put(curationSetName, new CurationSet(curationSetName, this.apiCall)); + } + + retVal = this.individualCurationSets.get(curationSetName); + return retVal; + } } diff --git a/src/main/java/org/typesense/api/Collection.java b/src/main/java/org/typesense/api/Collection.java index 6f2acab..cc5524f 100644 --- a/src/main/java/org/typesense/api/Collection.java +++ b/src/main/java/org/typesense/api/Collection.java @@ -21,9 +21,6 @@ public class Collection { private Synonyms synonyms; private Map individualSynonyms; - private Overrides overrides; - private Map individualOverrides; - private final String endpoint; Collection(String name, ApiCall apiCall, Configuration configuration) { @@ -35,8 +32,6 @@ public class Collection { this.individualDocuments = new HashMap<>(); this.synonyms = new Synonyms(this.name, this.apiCall); this.individualSynonyms = new HashMap<>(); - this.overrides = new Overrides(this.name, this.apiCall); - this.individualOverrides = new HashMap<>(); } public CollectionResponse retrieve() throws Exception { @@ -66,11 +61,31 @@ public Document documents(String documentId) { return retVal; } + /** + * @deprecated This method is deprecated and will be removed in a future version. + * Use {@link Client#synonymSets()} instead for the new synonym sets API. + * + * Note: The old synonyms API is only available on Typesense v29.0 and below. + * For Typesense v30.0 and above, use the new synonym sets API. + */ + @Deprecated public Synonyms synonyms() { + System.err.println("DEPRECATED: Using deprecated synonyms API. This API is only available on Typesense v29.0 and below. " + + "For Typesense v30.0 and above, use the new synonym sets API via Client.synonymSets()."); return this.synonyms; } + /** + * @deprecated This method is deprecated and will be removed in a future version. + * Use {@link Client#synonymSet(String)} instead for the new synonym sets API. + * + * Note: The old synonyms API is only available on Typesense v29.0 and below. + * For Typesense v30.0 and above, use the new synonym sets API. + */ + @Deprecated public Synonym synonyms(String synonymId) { + System.err.println("DEPRECATED: Using deprecated synonyms API. This API is only available on Typesense v29.0 and below. " + + "For Typesense v30.0 and above, use the new synonym sets API via Client.synonymSet(String)."); Synonym retVal; if (!this.individualSynonyms.containsKey(synonymId)) { @@ -80,19 +95,4 @@ public Synonym synonyms(String synonymId) { retVal = this.individualSynonyms.get(synonymId); return retVal; } - - public Overrides overrides() { - return this.overrides; - } - - public Override overrides(String overrideId) { - Override retVal; - - if (!this.individualOverrides.containsKey(overrideId)) { - this.individualOverrides.put(overrideId, new Override(this.name, overrideId, this.apiCall)); - } - - retVal = this.individualOverrides.get(overrideId); - return retVal; - } } diff --git a/src/main/java/org/typesense/api/CurationSet.java b/src/main/java/org/typesense/api/CurationSet.java new file mode 100644 index 0000000..8d9f8a1 --- /dev/null +++ b/src/main/java/org/typesense/api/CurationSet.java @@ -0,0 +1,33 @@ +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; +import org.typesense.model.CurationSetCreateSchema; +import org.typesense.model.CurationSetSchema; +import org.typesense.model.CurationSetDeleteSchema; + +public class CurationSet { + + private String curationSetName; + private ApiCall apiCall; + + public CurationSet(String curationSetName, ApiCall apiCall) { + this.curationSetName = curationSetName; + this.apiCall = apiCall; + } + + public CurationSetCreateSchema retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, CurationSetCreateSchema.class); + } + + public CurationSetSchema upsert(CurationSetCreateSchema curationSetCreateSchema) throws Exception { + return this.apiCall.put(this.getEndpoint(), curationSetCreateSchema, null, CurationSetSchema.class); + } + + public CurationSetDeleteSchema delete() throws Exception { + return this.apiCall.delete(this.getEndpoint(), null, CurationSetDeleteSchema.class); + } + + public String getEndpoint() { + return "/curation_sets/" + URLEncoding.encodeURIComponent(this.curationSetName); + } +} diff --git a/src/main/java/org/typesense/api/CurationSets.java b/src/main/java/org/typesense/api/CurationSets.java new file mode 100644 index 0000000..5bcb197 --- /dev/null +++ b/src/main/java/org/typesense/api/CurationSets.java @@ -0,0 +1,26 @@ +package org.typesense.api; + +import org.typesense.model.CurationSetCreateSchema; +import org.typesense.model.CurationSetSchema; + +public class CurationSets { + + private ApiCall apiCall; + public final static String RESOURCEPATH = "/curation_sets"; + + public CurationSets(ApiCall apiCall) { + this.apiCall = apiCall; + } + + public CurationSetSchema upsert(String curationSetName, CurationSetCreateSchema curationSetCreateSchema) throws Exception { + return this.apiCall.put(getEndpoint(curationSetName), curationSetCreateSchema, null, CurationSetSchema.class); + } + + public CurationSetSchema[] retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(null), null, CurationSetSchema[].class); + } + + public String getEndpoint(String operation) { + return RESOURCEPATH + "/" + (operation == null ? "" : operation); + } +} diff --git a/src/main/java/org/typesense/api/MultiSearch.java b/src/main/java/org/typesense/api/MultiSearch.java index 6878876..12ac458 100644 --- a/src/main/java/org/typesense/api/MultiSearch.java +++ b/src/main/java/org/typesense/api/MultiSearch.java @@ -1,6 +1,7 @@ package org.typesense.api; import org.typesense.model.MultiSearchResult; +import org.typesense.model.SearchResult; import org.typesense.model.MultiSearchSearchesParameter; import java.util.Map; @@ -14,8 +15,84 @@ public MultiSearch(ApiCall apiCall) { this.apiCall = apiCall; } + /** + * Performs a federated multi-search, returning individual result sets for each + * query. + * + * This method is used for running multiple search queries in a single API call, + * where + * the results for each query are returned as separate and distinct sets. This + * is + * also known as a + * Federated Search. + *
+ * This method strictly handles non-union searches. It will validate that the + * {@code union} flag is set to {@code false}. If the flag is {@code true}, it + * will + * throw an {@link IllegalArgumentException} to prevent unexpected behavior. For + * union + * searches, use the {@link #performUnion(MultiSearchSearchesParameter, Map)} + * method instead. + * + * @param multiSearchParameters The object containing the list of search queries + * to perform. + * The {@code union} flag must be {@code false} or + * unset. + * @param common_params A map of common parameters that will be applied + * to every + * search query in the request. Can be null or + * empty. + * @return A {@link MultiSearchResult} object containing a list of individual + * search + * results. The order of results in this list is guaranteed to match the + * order of the queries sent in the request. + * @throws IllegalArgumentException if the {@code union} flag in + * {@code multiSearchParameters} + * is set to {@code true}, as this method is + * strictly for + * non-union federated searches. + * @throws Exception if there is an issue with the API call, such + * as a network + * problem or an error response from the + * server. + */ public MultiSearchResult perform(MultiSearchSearchesParameter multiSearchParameters, - Map
common_params) throws Exception { - return this.apiCall.post(MultiSearch.RESOURCEPATH, multiSearchParameters, common_params, MultiSearchResult.class); + Map common_params) throws Exception { + if (multiSearchParameters.isUnion()) { + throw new IllegalArgumentException( + "The 'perform()' method is for non-union searches. For a union search, please use the 'performUnion()' method."); + } + return this.apiCall.post(MultiSearch.RESOURCEPATH, multiSearchParameters, common_params, + MultiSearchResult.class); + } + + /** + * Performs a Union Search + * and returns a single, combined search result. + * + * This method offers a convenient way to perform a union search. It + * automatically + * enforces {@code union=true}, merging results from all queries into a single + * ordered set of hits without requiring you to set the flag manually. + *
+ * This method is guaranteed to not modify the provided + * {@code multiSearchParameters} + * object, making it safe to reuse the same parameter object across multiple API + * calls. + * + */ + public SearchResult performUnion(MultiSearchSearchesParameter multiSearchParameters, + Map
common_params) throws Exception { + // Create a shallow copy to safely enforce union=true without modifying the + // caller's original parameters. + MultiSearchSearchesParameter copiedParams = new MultiSearchSearchesParameter(); + copiedParams.setSearches(multiSearchParameters.getSearches()); + copiedParams.setUnion(true); + + return this.apiCall.post(MultiSearch.RESOURCEPATH, copiedParams, common_params, SearchResult.class); } } diff --git a/src/main/java/org/typesense/api/Override.java b/src/main/java/org/typesense/api/Override.java deleted file mode 100644 index e81fb96..0000000 --- a/src/main/java/org/typesense/api/Override.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.typesense.api; - -import org.typesense.api.utils.URLEncoding; -import org.typesense.model.SearchOverride; - -public class Override { - - private String collectionName; - private String overrideId; - private ApiCall apiCall; - - public Override(String collectionName, String overrideId, ApiCall apiCall) { - this.collectionName = collectionName; - this.overrideId = overrideId; - this.apiCall = apiCall; - } - - public SearchOverride retrieve() throws Exception { - return this.apiCall.get(this.getEndpoint(), null, SearchOverride.class); - } - - public SearchOverride delete() throws Exception { - return this.apiCall.delete(this.getEndpoint(), null, SearchOverride.class); - } - - public String getEndpoint() { - return Collections.RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(this.collectionName) + "/" - + Overrides.RESOURCEPATH + "/" + URLEncoding.encodeURIComponent(this.overrideId); - } -} diff --git a/src/main/java/org/typesense/api/Overrides.java b/src/main/java/org/typesense/api/Overrides.java deleted file mode 100644 index 5bb9acd..0000000 --- a/src/main/java/org/typesense/api/Overrides.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.typesense.api; - -import org.typesense.api.utils.URLEncoding; -import org.typesense.model.SearchOverride; -import org.typesense.model.SearchOverrideSchema; -import org.typesense.model.SearchOverridesResponse; - -public class Overrides { - - public static String RESOURCEPATH = "/overrides"; - - private String collectionName; - private ApiCall apiCall; - - public Overrides(String collectionName, ApiCall apiCall) { - this.collectionName = collectionName; - this.apiCall = apiCall; - } - - public SearchOverride upsert(String overrideId, SearchOverrideSchema searchOverrideSchema) throws Exception { - return this.apiCall.put(getEndpoint(overrideId), searchOverrideSchema, null, SearchOverride.class); - } - - public SearchOverridesResponse retrieve() throws Exception { - return this.apiCall.get(this.getEndpoint(null), null, SearchOverridesResponse.class); - } - - public String getEndpoint(String operation) { - return Collections.RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(this.collectionName) - + Overrides.RESOURCEPATH + "/" + (operation == null ? "" - : URLEncoding.encodeURIComponent(operation)); - } -} diff --git a/src/main/java/org/typesense/api/Stemming.java b/src/main/java/org/typesense/api/Stemming.java new file mode 100644 index 0000000..0942c5d --- /dev/null +++ b/src/main/java/org/typesense/api/Stemming.java @@ -0,0 +1,32 @@ +package org.typesense.api; + +import java.util.HashMap; +import java.util.Map; + +public class Stemming { + private final ApiCall apiCall; + private final StemmingDictionaries dictionaries; + private final Map individualDictionaries; + + + public Stemming(ApiCall apiCall) { + this.apiCall = apiCall; + this.dictionaries = new StemmingDictionaries(this.apiCall); + this.individualDictionaries = new HashMap<>(); + } + + public StemmingDictionaries dictionaries() { + return this.dictionaries; + } + + public StemmingDictionary dictionaries(String dictionaryId) { + StemmingDictionary retVal; + + if (!this.individualDictionaries.containsKey(dictionaryId)) { + this.individualDictionaries.put(dictionaryId, new StemmingDictionary(dictionaryId, apiCall)); + } + + retVal = this.individualDictionaries.get(dictionaryId); + return retVal; + } +} diff --git a/src/main/java/org/typesense/api/StemmingDictionaries.java b/src/main/java/org/typesense/api/StemmingDictionaries.java new file mode 100644 index 0000000..f97842b --- /dev/null +++ b/src/main/java/org/typesense/api/StemmingDictionaries.java @@ -0,0 +1,59 @@ +package org.typesense.api; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.typesense.model.StemmingDictionaryWords; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class StemmingDictionaries { + private final ApiCall apiCall; + public final static String RESOURCE_PATH = "/stemming/dictionaries"; + + public StemmingDictionaries(ApiCall apiCall) { + this.apiCall = apiCall; + } + + public String upsert(String dictionaryId, String wordRootCombinations) throws Exception { + Map params = Collections.singletonMap("id", dictionaryId); + + return this.apiCall.post(this.getEndPoint("import"), wordRootCombinations, params, String.class); + } + + public List upsert(String dictionaryId, List wordRootCombinations) + throws Exception { + ObjectMapper mapper = new ObjectMapper(); + List jsonLines = new ArrayList<>(); + List objectList = new ArrayList<>(); + + for (StemmingDictionaryWords word : wordRootCombinations) { + jsonLines.add(mapper.writeValueAsString(word)); + } + + String reqBody = String.join("\n", jsonLines); + + Map params = Collections.singletonMap("id", dictionaryId); + + String resInJsonLineFormat = this.apiCall.post(this.getEndPoint("import"), reqBody, params, String.class); + + for (String line : resInJsonLineFormat.split("\n")) { + objectList.add(mapper.readValue(line, StemmingDictionaryWords.class)); + } + + return objectList; + } + + public StemmingDictionariesRetrieveSchema retrieve() throws Exception { + StemmingDictionariesRetrieveSchema response = this.apiCall.get(RESOURCE_PATH, null, + StemmingDictionariesRetrieveSchema.class); + return response != null ? response : new StemmingDictionariesRetrieveSchema(); + } + + public String getEndPoint(String target) { + return RESOURCE_PATH + "/" + target; + } + +} \ No newline at end of file diff --git a/src/main/java/org/typesense/api/StemmingDictionariesRetrieveSchema.java b/src/main/java/org/typesense/api/StemmingDictionariesRetrieveSchema.java new file mode 100644 index 0000000..aa956ac --- /dev/null +++ b/src/main/java/org/typesense/api/StemmingDictionariesRetrieveSchema.java @@ -0,0 +1,15 @@ +package org.typesense.api; + +import java.util.List; + +public class StemmingDictionariesRetrieveSchema { + private List dictionaries; + + public List getDictionaries() { + return dictionaries; + } + + public void setDictionaries(List dictionaries) { + this.dictionaries = dictionaries; + } +} \ No newline at end of file diff --git a/src/main/java/org/typesense/api/StemmingDictionary.java b/src/main/java/org/typesense/api/StemmingDictionary.java new file mode 100644 index 0000000..b13e69f --- /dev/null +++ b/src/main/java/org/typesense/api/StemmingDictionary.java @@ -0,0 +1,23 @@ +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; + +public class StemmingDictionary { + private final ApiCall apiCall; + private final String dictionaryId; + + public StemmingDictionary(String dictionaryId, ApiCall apiCall) { + this.apiCall = apiCall; + this.dictionaryId = dictionaryId; + } + + + public org.typesense.model.StemmingDictionary retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, org.typesense.model.StemmingDictionary.class); + } + + private String getEndpoint() { + return StemmingDictionaries.RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(this.dictionaryId); + } + +} \ No newline at end of file diff --git a/src/main/java/org/typesense/api/Stopwords.java b/src/main/java/org/typesense/api/Stopwords.java new file mode 100644 index 0000000..921e43b --- /dev/null +++ b/src/main/java/org/typesense/api/Stopwords.java @@ -0,0 +1,29 @@ +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; +import org.typesense.model.StopwordsSetSchema; +import org.typesense.model.StopwordsSetUpsertSchema; +import org.typesense.model.StopwordsSetsRetrieveAllSchema; + +public class Stopwords { + public final static String RESOURCEPATH = "/stopwords"; + + private final ApiCall apiCall; + + public Stopwords(ApiCall apiCall) { + this.apiCall = apiCall; + } + + public StopwordsSetSchema upsert(String stopwordSetId, StopwordsSetUpsertSchema stopwordSet) throws Exception { + return this.apiCall.put(getEndpoint(stopwordSetId), stopwordSet, null, StopwordsSetSchema.class); + } + + public StopwordsSetsRetrieveAllSchema retrieve() throws Exception { + return this.apiCall.get(Stopwords.RESOURCEPATH, null, StopwordsSetsRetrieveAllSchema.class); + } + + private String getEndpoint(String stopwordSetId) { + return RESOURCEPATH + "/" + URLEncoding.encodeURIComponent(stopwordSetId); + } + +} diff --git a/src/main/java/org/typesense/api/StopwordsSet.java b/src/main/java/org/typesense/api/StopwordsSet.java new file mode 100644 index 0000000..9200960 --- /dev/null +++ b/src/main/java/org/typesense/api/StopwordsSet.java @@ -0,0 +1,28 @@ +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; +import org.typesense.model.StopwordsSetRetrieveSchema; +import org.typesense.model.StopwordsSetSchema; + +public class StopwordsSet { + private final ApiCall apiCall; + private final String stopwordsSetId; + + public StopwordsSet(String stopwordsSetId, ApiCall apiCall) { + this.stopwordsSetId = stopwordsSetId; + this.apiCall = apiCall; + } + + public StopwordsSetRetrieveSchema retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, StopwordsSetRetrieveSchema.class); + } + + public StopwordsSetSchema delete() throws Exception { + return this.apiCall.delete(this.getEndpoint(), null, StopwordsSetSchema.class); + } + + private String getEndpoint() { + return Stopwords.RESOURCEPATH + "/" + URLEncoding.encodeURIComponent(this.stopwordsSetId); + } + +} diff --git a/src/main/java/org/typesense/api/Synonym.java b/src/main/java/org/typesense/api/Synonym.java index d7e0340..1a8affe 100644 --- a/src/main/java/org/typesense/api/Synonym.java +++ b/src/main/java/org/typesense/api/Synonym.java @@ -3,6 +3,15 @@ import org.typesense.api.utils.URLEncoding; import org.typesense.model.SearchSynonym; +/** + * @deprecated This class is deprecated and will be removed in a future version. + * Use {@link SynonymSet} instead for the new synonym sets API. + * + * Note: The old synonyms API is only available on Typesense v29.0 and below. + * For Typesense v30.0 and above, use the new synonym sets API. + */ +@Deprecated + public class Synonym { private String collectionName; diff --git a/src/main/java/org/typesense/api/SynonymSet.java b/src/main/java/org/typesense/api/SynonymSet.java new file mode 100644 index 0000000..4672409 --- /dev/null +++ b/src/main/java/org/typesense/api/SynonymSet.java @@ -0,0 +1,34 @@ +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; + +import org.typesense.model.SynonymSetCreateSchema; +import org.typesense.model.SynonymSetSchema; +import org.typesense.model.SynonymSetDeleteSchema; + +public class SynonymSet { + + private String synonymSetName; + private ApiCall apiCall; + + public SynonymSet(String synonymSetName, ApiCall apiCall) { + this.synonymSetName = synonymSetName; + this.apiCall = apiCall; + } + + public SynonymSetCreateSchema retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, SynonymSetCreateSchema.class); + } + + public SynonymSetSchema upsert(SynonymSetCreateSchema synonymSetCreateSchema) throws Exception { + return this.apiCall.put(this.getEndpoint(), synonymSetCreateSchema, null, SynonymSetSchema.class); + } + + public SynonymSetDeleteSchema delete() throws Exception { + return this.apiCall.delete(this.getEndpoint(), null, SynonymSetDeleteSchema.class); + } + + public String getEndpoint() { + return "/synonym_sets/" + URLEncoding.encodeURIComponent(this.synonymSetName); + } +} \ No newline at end of file diff --git a/src/main/java/org/typesense/api/SynonymSets.java b/src/main/java/org/typesense/api/SynonymSets.java new file mode 100644 index 0000000..764cb3c --- /dev/null +++ b/src/main/java/org/typesense/api/SynonymSets.java @@ -0,0 +1,27 @@ +package org.typesense.api; + +import java.util.List; +import org.typesense.model.SynonymSetCreateSchema; +import org.typesense.model.SynonymSetSchema; + +public class SynonymSets { + + private ApiCall apiCall; + public final static String RESOURCEPATH = "/synonym_sets"; + + public SynonymSets(ApiCall apiCall) { + this.apiCall = apiCall; + } + + public SynonymSetSchema upsert(String synonymSetName, SynonymSetCreateSchema synonymSetCreateSchema) throws Exception { + return this.apiCall.put(getEndpoint(synonymSetName), synonymSetCreateSchema, null, SynonymSetSchema.class); + } + + public SynonymSetSchema[] retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(null), null, SynonymSetSchema[].class); + } + + public String getEndpoint(String operation) { + return RESOURCEPATH + "/" + (operation == null ? "" : operation); + } +} \ No newline at end of file diff --git a/src/main/java/org/typesense/api/Synonyms.java b/src/main/java/org/typesense/api/Synonyms.java index 7f5a9ac..e46fc8b 100644 --- a/src/main/java/org/typesense/api/Synonyms.java +++ b/src/main/java/org/typesense/api/Synonyms.java @@ -5,6 +5,15 @@ import org.typesense.model.SearchSynonymSchema; import org.typesense.model.SearchSynonymsResponse; +/** + * @deprecated This class is deprecated and will be removed in a future version. + * Use {@link SynonymSets} instead for the new synonym sets API. + * + * Note: The old synonyms API is only available on Typesense v29.0 and below. + * For Typesense v30.0 and above, use the new synonym sets API. + */ +@Deprecated + public class Synonyms { private String collectionName; diff --git a/src/test/java/org/typesense/api/AnalyticsEventsTest.java b/src/test/java/org/typesense/api/AnalyticsEventsTest.java new file mode 100644 index 0000000..036a2ee --- /dev/null +++ b/src/test/java/org/typesense/api/AnalyticsEventsTest.java @@ -0,0 +1,62 @@ +package org.typesense.api; + +import java.util.HashMap; + +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.model.AnalyticsEvent; +import org.typesense.model.AnalyticsEventCreateResponse; +import org.typesense.model.AnalyticsEventData; + +public class AnalyticsEventsTest { + + private Client client; + private Helper helper; + + private String ruleName; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + + if (!Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + + helper.teardown(); + helper.createTestCollection(); + helper.createTestQueryCollection(); + helper.createTestQueryDocument(); + helper.createTestAnalyticsCollection(); + String ruleName = helper.createTestAnalyticsRule(); + + this.ruleName = ruleName; + } + + @AfterEach + void tearDown() throws Exception { + helper.teardown(); + } + + @Test + void testCreate() throws Exception { + + AnalyticsEventData eventData = new AnalyticsEventData() + .q("running shoes") + .userId("1234") + .docId("query1"); + + AnalyticsEvent analyticsEvent = new AnalyticsEvent() + .eventType("search") + .name(ruleName) + .data(eventData); + + AnalyticsEventCreateResponse result = this.client.analytics().events().create(analyticsEvent); + assertNotNull(result); + assertEquals(true, result.isOk()); + } +} diff --git a/src/test/java/org/typesense/api/AnalyticsRulesTest.java b/src/test/java/org/typesense/api/AnalyticsRulesTest.java new file mode 100644 index 0000000..5415492 --- /dev/null +++ b/src/test/java/org/typesense/api/AnalyticsRulesTest.java @@ -0,0 +1,199 @@ +package org.typesense.api; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.typesense.model.AnalyticsRule; +import org.typesense.model.AnalyticsRuleCreate; +import org.typesense.model.AnalyticsRuleCreateParams; +import org.typesense.model.AnalyticsRuleType; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import org.typesense.model.InlineResponse2003; +import org.typesense.model.CollectionSchema; +import org.typesense.model.Field; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; + + +class AnalyticsRulesTest { + + private Client client; + private Helper helper; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + + if (!Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + + try { + CollectionSchema collectionSchema = new CollectionSchema() + .name("companies") + .fields(Arrays.asList( + new Field().name("company_name").type("string"), + new Field().name("num_employees").type("int32"), + new Field().name("country").type("string") + )); + client.collections().create(collectionSchema); + } catch (Exception e) { + } + + try { + CollectionSchema analyticsCollectionSchema = new CollectionSchema() + .name("analytics_data") + .fields(Arrays.asList( + new Field().name("rule_name").type("string"), + new Field().name("event_type").type("string"), + new Field().name("counter_value").type("int32"), + new Field().name("timestamp").type("int64") + )); + client.collections().create(analyticsCollectionSchema); + } catch (Exception e) { + } + } + + @AfterEach + void tearDown() throws Exception { + helper.teardown(); + } + + @Test + void testCreate() throws Exception { + + String ruleName = "test-rule-" + System.currentTimeMillis(); + + AnalyticsRuleCreate analyticsRule = new AnalyticsRuleCreate() + .name(ruleName) + .type(AnalyticsRuleType.COUNTER) + .collection("analytics_data") + .eventType("click") + .params(new AnalyticsRuleCreateParams() + .counterField("num_employees") + .weight(1)); + + List rules = new ArrayList<>(); + rules.add(analyticsRule); + + AnalyticsRules.AnalyticsRulesResponse result = this.client.analytics().rules().create(rules); + assertNotNull(result); + assertFalse(result.isEmpty()); + assertEquals(1, result.getCount()); + + AnalyticsRule createdRule = result.getFirstRule(); + assertNotNull(createdRule); + assertEquals(ruleName, createdRule.getName()); + assertEquals(AnalyticsRuleType.COUNTER, createdRule.getType()); + assertEquals("analytics_data", createdRule.getCollection()); + assertEquals("click", createdRule.getEventType()); + } + + @Test + void testRetrieve() throws Exception { + + String ruleName = "test-rule-" + System.currentTimeMillis(); + + AnalyticsRuleCreate analyticsRule = new AnalyticsRuleCreate() + .name(ruleName) + .type(AnalyticsRuleType.COUNTER) + .collection("analytics_data") + .eventType("click") + .params(new AnalyticsRuleCreateParams() + .counterField("num_employees") + .weight(1)); + + List rules = new ArrayList<>(); + rules.add(analyticsRule); + + this.client.analytics().rules().create(rules); + + List result = this.client.analytics().rules().retrieve(); + assertNotNull(result); + assertTrue(result.size() >= 1, "number of rules is invalid"); + + AnalyticsRule foundRule = null; + for (AnalyticsRule rule : result) { + if (ruleName.equals(rule.getName())) { + foundRule = rule; + break; + } + } + + assertNotNull(foundRule, "rule not found"); + assertEquals(ruleName, foundRule.getName()); + assertEquals(AnalyticsRuleType.COUNTER, foundRule.getType()); + assertEquals("analytics_data", foundRule.getCollection()); + assertEquals("click", foundRule.getEventType()); + } + + @Test + void testRetrieveSingle() throws Exception { + + String ruleName = "test-rule-" + System.currentTimeMillis(); + + AnalyticsRuleCreate analyticsRule = new AnalyticsRuleCreate() + .name(ruleName) + .type(AnalyticsRuleType.COUNTER) + .collection("analytics_data") + .eventType("click") + .params(new AnalyticsRuleCreateParams() + .counterField("num_employees") + .weight(1)); + + List rules = new ArrayList<>(); + rules.add(analyticsRule); + + this.client.analytics().rules().create(rules); + + AnalyticsRule result = this.client.analytics().rules(ruleName).retrieve(); + assertNotNull(result); + assertEquals(ruleName, result.getName()); + assertEquals(AnalyticsRuleType.COUNTER, result.getType()); + assertEquals("analytics_data", result.getCollection()); + assertEquals("click", result.getEventType()); + } + + @Test + void testDelete() throws Exception { + + String ruleName = "test-rule-" + System.currentTimeMillis(); + + AnalyticsRuleCreate analyticsRule = new AnalyticsRuleCreate() + .name(ruleName) + .type(AnalyticsRuleType.COUNTER) + .collection("analytics_data") + .eventType("click") + .params(new AnalyticsRuleCreateParams() + .counterField("num_employees") + .weight(1)); + + List rules = new ArrayList<>(); + rules.add(analyticsRule); + + this.client.analytics().rules().create(rules); + + AnalyticsRule result = this.client.analytics().rules(ruleName).delete(); + assertNotNull(result); + assertEquals(ruleName, result.getName()); + + try { + this.client.analytics().rules(ruleName).retrieve(); + fail("Rule should have been deleted"); + } catch (Exception e) { + assertTrue(e.getMessage().contains("not found") || e.getMessage().contains("404")); + } + } +} diff --git a/src/test/java/org/typesense/api/CurationSetTest.java b/src/test/java/org/typesense/api/CurationSetTest.java new file mode 100644 index 0000000..fb5a0b3 --- /dev/null +++ b/src/test/java/org/typesense/api/CurationSetTest.java @@ -0,0 +1,131 @@ +package org.typesense.api; + +import java.util.ArrayList; +import java.util.Arrays; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.model.CurationItemCreateSchema; +import org.typesense.model.CurationSetCreateSchema; +import org.typesense.model.CurationSetSchema; +import org.typesense.model.CurationSetDeleteSchema; +import org.typesense.model.CurationRule; +import org.typesense.model.CurationInclude; +import org.typesense.model.CurationExclude; + +import static org.junit.jupiter.api.Assertions.*; + +class CurationSetTest { + + private Client client; + private Helper helper; + private String curationSetName; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + + if (!Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + + helper.teardown(); + helper.createTestCollection(); + curationSetName = "test-curation-set-" + System.currentTimeMillis(); + } + + @AfterEach + void tearDown() throws Exception { + try { + client.curationSet(curationSetName).delete(); + } catch (Exception e) { + } + helper.teardown(); + } + + @Test + void testRetrieve() throws Exception { + CurationSetCreateSchema curationSetData = helper.createTestCurationSetData(); + client.curationSets().upsert(curationSetName, curationSetData); + + CurationSetCreateSchema result = client.curationSet(curationSetName).retrieve(); + + assertNotNull(result); + assertEquals(1, result.getItems().size()); + assertEquals("dummy", result.getItems().get(0).getId()); + assertEquals("apple", result.getItems().get(0).getRule().getQuery()); + assertNotNull(result.getItems().get(0).getIncludes()); + assertEquals(2, result.getItems().get(0).getIncludes().size()); + assertEquals("422", result.getItems().get(0).getIncludes().get(0).getId()); + assertEquals("54", result.getItems().get(0).getIncludes().get(1).getId()); + assertNotNull(result.getItems().get(0).getExcludes()); + assertEquals(1, result.getItems().get(0).getExcludes().size()); + assertEquals("287", result.getItems().get(0).getExcludes().get(0).getId()); + } + + @Test + void testUpdate() throws Exception { + CurationSetCreateSchema originalData = helper.createTestCurationSetData(); + client.curationSets().upsert(curationSetName, originalData); + + CurationRule updatedRule = new CurationRule(); + updatedRule.setQuery("updated query"); + updatedRule.setMatch(CurationRule.MatchEnum.EXACT); + + CurationInclude include1 = new CurationInclude(); + include1.setId("422"); + include1.setPosition(1); + CurationInclude include2 = new CurationInclude(); + include2.setId("54"); + include2.setPosition(2); + CurationInclude include3 = new CurationInclude(); + include3.setId("999"); + include3.setPosition(3); + + CurationExclude exclude1 = new CurationExclude(); + exclude1.setId("287"); + + CurationItemCreateSchema updatedItem = new CurationItemCreateSchema(); + updatedItem.setId("dummy"); + updatedItem.setRule(updatedRule); + updatedItem.setIncludes(Arrays.asList(include1, include2, include3)); + updatedItem.setExcludes(Arrays.asList(exclude1)); + updatedItem.setRemoveMatchedTokens(true); + updatedItem.setFilterBy("category:=Electronics"); + updatedItem.setStopProcessing(true); + + CurationSetCreateSchema updatedData = new CurationSetCreateSchema(); + updatedData.setItems(Arrays.asList(updatedItem)); + updatedData.setDescription("Updated test curation set"); + + CurationSetSchema result = client.curationSet(curationSetName).upsert(updatedData); + + assertEquals(curationSetName, result.getName()); + assertEquals(1, result.getItems().size()); + assertEquals("dummy", result.getItems().get(0).getId()); + assertEquals("updated query", result.getItems().get(0).getRule().getQuery()); + assertNotNull(result.getItems().get(0).getIncludes()); + assertEquals(3, result.getItems().get(0).getIncludes().size()); + assertEquals("422", result.getItems().get(0).getIncludes().get(0).getId()); + assertEquals("54", result.getItems().get(0).getIncludes().get(1).getId()); + assertEquals("999", result.getItems().get(0).getIncludes().get(2).getId()); + assertNotNull(result.getItems().get(0).getExcludes()); + assertEquals(1, result.getItems().get(0).getExcludes().size()); + assertEquals("287", result.getItems().get(0).getExcludes().get(0).getId()); + } + + @Test + void testDelete() throws Exception { + CurationSetCreateSchema curationSetData = helper.createTestCurationSetData(); + client.curationSets().upsert(curationSetName, curationSetData); + + CurationSetDeleteSchema result = client.curationSet(curationSetName).delete(); + + assertEquals(curationSetName, result.getName()); + + assertThrows(Exception.class, () -> { + client.curationSet(curationSetName).retrieve(); + }); + } +} diff --git a/src/test/java/org/typesense/api/CurationSetsTest.java b/src/test/java/org/typesense/api/CurationSetsTest.java new file mode 100644 index 0000000..1488952 --- /dev/null +++ b/src/test/java/org/typesense/api/CurationSetsTest.java @@ -0,0 +1,92 @@ +package org.typesense.api; + +import java.util.Arrays; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.model.CurationItemCreateSchema; +import org.typesense.model.CurationSetCreateSchema; +import org.typesense.model.CurationSetSchema; + +import static org.junit.jupiter.api.Assertions.*; + +class CurationSetsTest { + + private Client client; + private Helper helper; + private String curationSetName; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + + if (!Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + + helper.teardown(); + helper.createTestCollection(); + curationSetName = "test-curation-set-" + System.currentTimeMillis(); + } + + @AfterEach + void tearDown() throws Exception { + try { + client.curationSet(curationSetName).delete(); + } catch (Exception e) { + } + helper.teardown(); + } + + @Test + void testUpsert() throws Exception { + CurationSetCreateSchema curationSetData = helper.createTestCurationSetData(); + + CurationSetSchema result = client.curationSets().upsert(curationSetName, curationSetData); + + assertEquals(curationSetName, result.getName()); + assertEquals(1, result.getItems().size()); + assertEquals("dummy", result.getItems().get(0).getId()); + assertEquals("apple", result.getItems().get(0).getRule().getQuery()); + assertNotNull(result.getItems().get(0).getIncludes()); + assertEquals(2, result.getItems().get(0).getIncludes().size()); + assertEquals("422", result.getItems().get(0).getIncludes().get(0).getId()); + assertEquals("54", result.getItems().get(0).getIncludes().get(1).getId()); + assertNotNull(result.getItems().get(0).getExcludes()); + assertEquals(1, result.getItems().get(0).getExcludes().size()); + assertEquals("287", result.getItems().get(0).getExcludes().get(0).getId()); + } + + @Test + void testRetrieve() throws Exception { + CurationSetCreateSchema curationSetData = helper.createTestCurationSetData(); + client.curationSets().upsert(curationSetName, curationSetData); + + CurationSetSchema[] result = client.curationSets().retrieve(); + + assertNotNull(result); + assertTrue(result.length >= 1); + + CurationSetSchema foundCurationSet = null; + for (CurationSetSchema cs : result) { + if (curationSetName.equals(cs.getName())) { + foundCurationSet = cs; + break; + } + } + + assertNotNull(foundCurationSet); + assertEquals(curationSetName, foundCurationSet.getName()); + assertEquals(1, foundCurationSet.getItems().size()); + assertEquals("dummy", foundCurationSet.getItems().get(0).getId()); + assertEquals("apple", foundCurationSet.getItems().get(0).getRule().getQuery()); + assertNotNull(foundCurationSet.getItems().get(0).getIncludes()); + assertEquals(2, foundCurationSet.getItems().get(0).getIncludes().size()); + assertEquals("422", foundCurationSet.getItems().get(0).getIncludes().get(0).getId()); + assertEquals("54", foundCurationSet.getItems().get(0).getIncludes().get(1).getId()); + assertNotNull(foundCurationSet.getItems().get(0).getExcludes()); + assertEquals(1, foundCurationSet.getItems().get(0).getExcludes().size()); + assertEquals("287", foundCurationSet.getItems().get(0).getExcludes().get(0).getId()); + } +} diff --git a/src/test/java/org/typesense/api/Helper.java b/src/test/java/org/typesense/api/Helper.java index b9b943f..767aa30 100644 --- a/src/test/java/org/typesense/api/Helper.java +++ b/src/test/java/org/typesense/api/Helper.java @@ -1,5 +1,11 @@ package org.typesense.api; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Arrays; import org.typesense.model.ApiKey; import org.typesense.model.ApiKeySchema; import org.typesense.model.ApiKeysResponse; @@ -9,25 +15,70 @@ import org.typesense.model.CollectionResponse; import org.typesense.model.CollectionSchema; import org.typesense.model.Field; -import org.typesense.model.SearchOverrideInclude; -import org.typesense.model.SearchOverrideRule; -import org.typesense.model.SearchOverrideSchema; import org.typesense.model.SearchSynonymSchema; +import org.typesense.model.StemmingDictionaryWords; +import org.typesense.model.StopwordsSetSchema; +import org.typesense.model.StopwordsSetUpsertSchema; +import org.typesense.model.StopwordsSetsRetrieveAllSchema; +import org.typesense.model.SynonymItemSchema; +import org.typesense.model.SynonymSetCreateSchema; +import org.typesense.model.SynonymSetSchema; +import org.typesense.model.SynonymSetsRetrieveSchema; +import org.typesense.model.CurationItemCreateSchema; +import org.typesense.model.CurationSetCreateSchema; +import org.typesense.model.CurationRule; +import org.typesense.model.CurationInclude; +import org.typesense.model.CurationExclude; import org.typesense.resources.Node; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import org.typesense.model.AnalyticsRuleCreate; +import org.typesense.model.AnalyticsRuleCreateParams; +import org.typesense.model.AnalyticsRule; +import org.typesense.model.AnalyticsRuleType; +import org.typesense.api.AnalyticsRules; public class Helper { private final Client client; + public static boolean isV30OrAbove(Client client) { + try { + Map debugResponse = client.debug.retrieve(); + if (debugResponse == null) { + return false; + } + + Object version = debugResponse.get("version"); + if (version == null) { + return false; + } + + String versionStr = version.toString(); + if ("nightly".equals(versionStr)) { + return true; + } + + String numberedVersion = versionStr; + if (versionStr.startsWith("v")) { + numberedVersion = versionStr.substring(1); + } + + String[] parts = numberedVersion.split("\\."); + if (parts.length == 0) { + return false; + } + + int majorVersion = Integer.parseInt(parts[0]); + return majorVersion >= 30; + + } catch (Exception e) { + return false; + } + } + Helper() { List nodes = new ArrayList<>(); - nodes.add(new Node("http","localhost","8108")); + nodes.add(new Node("http", "localhost", "8108")); - Configuration configuration = new Configuration(nodes, Duration.ofSeconds(3),"xyz"); + Configuration configuration = new Configuration(nodes, Duration.ofSeconds(3), "xyz"); this.client = new Client(configuration); } @@ -42,15 +93,35 @@ public void createTestCollection() throws Exception { client.collections().create(collectionSchema); } + public void createTestQueryCollection() throws Exception { + List fields = new ArrayList<>(); + fields.add(new Field().name("q").type((FieldTypes.STRING))); + fields.add(new Field().name("count").type((FieldTypes.INT32))); + + CollectionSchema collectionSchema = new CollectionSchema(); + collectionSchema.name("queries").fields(fields); + + client.collections().create(collectionSchema); + } + + public void createTestQueryDocument() throws Exception { + HashMap hmap = new HashMap<>(); + hmap.put("q", "running shoes"); + hmap.put("count", 42); + hmap.put("id", "query1"); + + client.collections("queries").documents().create(hmap); + } + public void createTestDocument() throws Exception { - String[] authors = {"shakspeare","william"}; + String[] authors = { "shakspeare", "william" }; HashMap hmap = new HashMap<>(); - hmap.put("title","Romeo and juliet"); - hmap.put("authors",authors); - hmap.put("publication_year",1666); - hmap.put("ratings_count",124); - hmap.put("average_rating",3.2); - hmap.put("id","1"); + hmap.put("title", "Romeo and juliet"); + hmap.put("authors", authors); + hmap.put("publication_year", 1666); + hmap.put("ratings_count", 124); + hmap.put("average_rating", 3.2); + hmap.put("id", "1"); client.collections("books").documents().create(hmap); } @@ -75,36 +146,150 @@ public ApiKey createTestKey() throws Exception { return client.keys().create(apiKeySchema); } - public void createTestOverrirde() throws Exception { - SearchOverrideSchema searchOverrideSchema = new SearchOverrideSchema(); - List searchOverrideIncludes = new ArrayList<>(); - searchOverrideIncludes.add(new SearchOverrideInclude().id("422").position(1)); - searchOverrideSchema.rule(new SearchOverrideRule().query("apple").match(SearchOverrideRule.MatchEnum.EXACT)) - .includes(searchOverrideIncludes); - client.collections("books").overrides().upsert("customize-apple", searchOverrideSchema); - } - public void createTestSynonym() throws Exception { SearchSynonymSchema synonym = new SearchSynonymSchema(); synonym.addSynonymsItem("blazer").addSynonymsItem("coat").addSynonymsItem("jacket"); - client.collections("books").synonyms().upsert("coat-synonyms",synonym); + client.collections("books").synonyms().upsert("coat-synonyms", synonym); + } + + public void createTestAnalyticsCollection() throws Exception { + List fields = new ArrayList<>(); + fields.add(new Field().name("rule_name").type(FieldTypes.STRING)); + fields.add(new Field().name("event_type").type(FieldTypes.STRING)); + fields.add(new Field().name("counter_value").type(FieldTypes.INT32)); + fields.add(new Field().name("timestamp").type(FieldTypes.INT64)); + + CollectionSchema collectionSchema = new CollectionSchema(); + collectionSchema.name("analytics_data").fields(fields); + + client.collections().create(collectionSchema); + } + + public String createTestAnalyticsRule() throws Exception { + String ruleName = "analytics-rule-" + System.currentTimeMillis(); + + AnalyticsRuleCreate analyticsRule = new AnalyticsRuleCreate() + .name(ruleName) + .type(AnalyticsRuleType.COUNTER) + .collection("analytics_data") + .eventType("click") + .params(new AnalyticsRuleCreateParams() + .counterField("num_employees") + .weight(1)); + + List rules = new ArrayList<>(); + rules.add(analyticsRule); + + client.analytics().rules().create(rules); + return ruleName; + } + + public void createTestStopwordsSet() throws Exception { + List stopwords = new ArrayList<>(); + stopwords.add("the"); + stopwords.add("of"); + stopwords.add("and"); + + StopwordsSetUpsertSchema stopwordsSetSchema = new StopwordsSetUpsertSchema(); + stopwordsSetSchema.stopwords(stopwords); + + client.stopwords().upsert("common-words", stopwordsSetSchema); + } + + + public void createStemmingDictionary() throws Exception{ + List stemmingDictionaryWords = new ArrayList<>(); + + stemmingDictionaryWords.add(new StemmingDictionaryWords().word("ran").root("run")); + stemmingDictionaryWords.add(new StemmingDictionaryWords().word("running").root("run")); + + client.stemming().dictionaries().upsert("irregular-plurals", stemmingDictionaryWords); + } + + public SynonymSetCreateSchema createTestSynonymSetData() { + SynonymItemSchema synonymItem = new SynonymItemSchema(); + synonymItem.setId("dummy"); + synonymItem.setSynonyms(Arrays.asList("foo", "bar", "baz")); + + SynonymSetCreateSchema synonymSetData = new SynonymSetCreateSchema(); + synonymSetData.setItems(Arrays.asList(synonymItem)); + return synonymSetData; + } + + public CurationSetCreateSchema createTestCurationSetData() { + CurationRule rule = new CurationRule(); + rule.setQuery("apple"); + rule.setMatch(CurationRule.MatchEnum.EXACT); + + CurationInclude include1 = new CurationInclude(); + include1.setId("422"); + include1.setPosition(1); + CurationInclude include2 = new CurationInclude(); + include2.setId("54"); + include2.setPosition(2); + + CurationExclude exclude1 = new CurationExclude(); + exclude1.setId("287"); + + CurationItemCreateSchema curationItem = new CurationItemCreateSchema(); + curationItem.setId("dummy"); + curationItem.setRule(rule); + curationItem.setIncludes(Arrays.asList(include1, include2)); + curationItem.setExcludes(Arrays.asList(exclude1)); + curationItem.setRemoveMatchedTokens(true); + curationItem.setFilterBy("category:=Electronics"); + curationItem.setStopProcessing(true); + + CurationSetCreateSchema curationSetData = new CurationSetCreateSchema(); + curationSetData.setItems(Arrays.asList(curationItem)); + curationSetData.setDescription("Test curation set"); + return curationSetData; } public void teardown() throws Exception { CollectionResponse[] collectionResponses = client.collections().retrieve(); - for(CollectionResponse c:collectionResponses) { - client.collections(c.getName()).delete(); + for (CollectionResponse c : collectionResponses) { + if (c.getName() != null) { + client.collections(c.getName()).delete(); + } } CollectionAliasesResponse collectionAliasesResponse = client.aliases().retrieve(); - for(CollectionAlias a:collectionAliasesResponse.getAliases()) { - client.aliases(a.getName()).delete(); + for (CollectionAlias a : collectionAliasesResponse.getAliases()) { + if (a.getName() != null) { + client.aliases(a.getName()).delete(); + } + } + + AnalyticsRules analyticsRules = client.analytics().rules(); + List rules = analyticsRules.retrieve(); + if (rules != null) { + for (AnalyticsRule r : rules) { + if (r.getName() != null) { + client.analytics().rules(r.getName()).delete(); + } + } } ApiKeysResponse apiKeysResponse = client.keys().retrieve(); - for (ApiKey k: apiKeysResponse.getKeys()) { + for (ApiKey k : apiKeysResponse.getKeys()) { client.keys(k.getId()).delete(); } + + StopwordsSetsRetrieveAllSchema stopwords = client.stopwords().retrieve(); + for (StopwordsSetSchema s : stopwords.getStopwords()) { + client.stopwords(s.getId()).delete(); + } + + try { + SynonymSetSchema[] synonymSets = client.synonymSets().retrieve(); + for (SynonymSetSchema s : synonymSets) { + if (s.getName() != null) { + client.synonymSet(s.getName()).delete(); + } + } + } catch (Exception e) { + } } } diff --git a/src/test/java/org/typesense/api/MultiSearchTest.java b/src/test/java/org/typesense/api/MultiSearchTest.java index 9259cbe..6b07e9c 100644 --- a/src/test/java/org/typesense/api/MultiSearchTest.java +++ b/src/test/java/org/typesense/api/MultiSearchTest.java @@ -7,14 +7,18 @@ import org.typesense.model.Field; import org.typesense.model.MultiSearchCollectionParameters; import org.typesense.model.MultiSearchResult; +import org.typesense.model.SearchResult; import org.typesense.model.MultiSearchSearchesParameter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; class MultiSearchTest { @@ -35,12 +39,23 @@ void setUp() throws Exception { CollectionSchema collectionSchema = new CollectionSchema(); collectionSchema.name("embeddings").fields(fields); client.collections().create(collectionSchema); + // create another collection for union search + collectionSchema.name("embeddings-2").fields(fields); + client.collections().create(collectionSchema); + + float[] vecVals = { 0.12f, 0.45f, 0.87f, 0.18f }; + Map doc1 = new HashMap<>(); + doc1.put("title", "Romeo and Juliet"); + doc1.put("vec", vecVals); + doc1.put("source", "embeddings_1"); + client.collections("embeddings").documents().create(doc1); - float[] vecVals = {0.12f, 0.45f, 0.87f, 0.18f}; - Map doc = new HashMap<>(); - doc.put("title", "Romeo and Juliet"); - doc.put("vec", vecVals); - client.collections("embeddings").documents().create(doc); + // Document for the second collection + Map doc2 = new HashMap<>(); + doc2.put("title", "Romeo and Juliet from collection 2"); + doc2.put("vec", new float[] { 0.12f, 0.45f, 0.87f, 0.18f }); + doc2.put("source", "embeddings_2"); + client.collections("embeddings-2").documents().create(doc2); } @AfterEach @@ -55,10 +70,36 @@ void testSearch() throws Exception { search1.setQ("*"); search1.setVectorQuery("vec:([0.96826,0.94,0.39557,0.306488], k:10)"); - MultiSearchSearchesParameter multiSearchParameters = new MultiSearchSearchesParameter().addSearchesItem(search1); + MultiSearchSearchesParameter multiSearchParameters = new MultiSearchSearchesParameter() + .addSearchesItem(search1); MultiSearchResult response = this.client.multiSearch.perform(multiSearchParameters, null); assertEquals(1, response.getResults().size()); assertEquals(1, response.getResults().get(0).getHits().size()); assertEquals("0", response.getResults().get(0).getHits().get(0).getDocument().get("id")); } + + @Test + void testUnionSearch() throws Exception { + MultiSearchCollectionParameters search1 = new MultiSearchCollectionParameters(); + search1.setCollection("embeddings"); + search1.setQ("*"); + + MultiSearchCollectionParameters search2 = new MultiSearchCollectionParameters(); + search2.setCollection("embeddings-2"); + search2.setQ("*"); + + MultiSearchSearchesParameter multiSearchParameters = new MultiSearchSearchesParameter() + .addSearchesItem(search1).addSearchesItem(search2); + SearchResult response = this.client.multiSearch.performUnion(multiSearchParameters, null); + + assertEquals(2, response.getHits().size()); + assertEquals(2, response.getUnionRequestParams().size()); + + Set sources = new HashSet<>(); + sources.add((String) response.getHits().get(0).getDocument().get("source")); + sources.add((String) response.getHits().get(1).getDocument().get("source")); + + assertTrue(sources.contains("embeddings_1"), "Results should contain a document from embeddings_1"); + assertTrue(sources.contains("embeddings_2"), "Results should contain a document from embeddings_2"); + } } diff --git a/src/test/java/org/typesense/api/OverridesTest.java b/src/test/java/org/typesense/api/OverridesTest.java deleted file mode 100644 index b034679..0000000 --- a/src/test/java/org/typesense/api/OverridesTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.typesense.api; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.typesense.model.SearchOverrideExclude; -import org.typesense.model.SearchOverrideInclude; -import org.typesense.model.SearchOverrideRule; -import org.typesense.model.SearchOverrideSchema; - -import java.util.ArrayList; -import java.util.List; - -class OverridesTest { - - private Client client; - private Helper helper; - - @BeforeEach - void setUp() throws Exception { - helper = new Helper(); - helper.teardown(); - client = helper.getClient(); - helper.createTestCollection(); - helper.createTestOverrirde(); - } - - @AfterEach - void tearDown() throws Exception { - helper.teardown(); - } - - @Test - void testUpsert() throws Exception { - SearchOverrideSchema searchOverrideSchema = new SearchOverrideSchema(); - - List searchOverrideIncludes = new ArrayList<>(); - searchOverrideIncludes.add(new SearchOverrideInclude().id("422").position(1)); - searchOverrideIncludes.add(new SearchOverrideInclude().id("54").position(2)); - - List searchOverrideExcludes = new ArrayList<>(); - searchOverrideExcludes.add(new SearchOverrideExclude().id("287")); - - searchOverrideSchema.rule(new SearchOverrideRule().query("apple").match(SearchOverrideRule.MatchEnum.EXACT)) - .includes(searchOverrideIncludes) - .excludes(searchOverrideExcludes); - - System.out.println(client.collections("books").overrides().upsert("apple", searchOverrideSchema)); - } - - @Test - void testRetrieveAll() throws Exception { - System.out.println(this.client.collections("books").overrides().retrieve()); - } - - @Test - void testRetrieve() throws Exception { - System.out.println(this.client.collections("books").overrides("customize-apple").retrieve()); - } - - @Test - void testDelete() throws Exception { - System.out.println(this.client.collections("books").overrides("customize-apple").delete()); - } -} \ No newline at end of file diff --git a/src/test/java/org/typesense/api/StemmingTest.java b/src/test/java/org/typesense/api/StemmingTest.java new file mode 100644 index 0000000..5122f68 --- /dev/null +++ b/src/test/java/org/typesense/api/StemmingTest.java @@ -0,0 +1,54 @@ +package org.typesense.api; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.model.StemmingDictionary; +import org.typesense.model.StemmingDictionaryWords; + +public class StemmingTest { + private Client client; + private Helper helper; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + helper.teardown(); + client = helper.getClient(); + helper.createStemmingDictionary(); + } + + @Test + void testUpsert() throws Exception { + List stemmingDictionaryWords = new ArrayList<>(); + + stemmingDictionaryWords.add(new StemmingDictionaryWords().word("ran").root("run")); + stemmingDictionaryWords.add(new StemmingDictionaryWords().word("running").root("run")); + + List res = client.stemming().dictionaries().upsert("irregular-plurals", + stemmingDictionaryWords); + + assertEquals(2, res.size()); + assertEquals("ran", res.get(0).getWord()); + assertEquals("run", res.get(0).getRoot()); + assertEquals("running", res.get(1).getWord()); + assertEquals("run", res.get(1).getRoot()); + } + + @Test + void testRetrieveOne() throws Exception { + StemmingDictionary res = client.stemming().dictionaries("irregular-plurals").retrieve(); + assertEquals("irregular-plurals", res.getId()); + assertEquals(2, res.getWords().size()); + } + + @Test + void testRetrieveAll() throws Exception { + StemmingDictionariesRetrieveSchema res = client.stemming().dictionaries().retrieve(); + assertEquals(1, res.getDictionaries().size()); + assertEquals("irregular-plurals", res.getDictionaries().get(0)); + } +} diff --git a/src/test/java/org/typesense/api/StopwordsTest.java b/src/test/java/org/typesense/api/StopwordsTest.java new file mode 100644 index 0000000..91aeb27 --- /dev/null +++ b/src/test/java/org/typesense/api/StopwordsTest.java @@ -0,0 +1,91 @@ +package org.typesense.api; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.model.StopwordsSetRetrieveSchema; +import org.typesense.model.StopwordsSetSchema; +import org.typesense.model.StopwordsSetUpsertSchema; +import org.typesense.model.StopwordsSetsRetrieveAllSchema; + +public class StopwordsTest { + + private Client client; + private Helper helper; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + helper.teardown(); + helper.createTestCollection(); + } + + @AfterEach + void tearDown() throws Exception { + helper.teardown(); + } + + @Test + void testUpsert() throws Exception { + List stopwords = new ArrayList<>(); + stopwords.add("the"); + stopwords.add("of"); + stopwords.add("and"); + + StopwordsSetUpsertSchema stopwordsSetSchema = new StopwordsSetUpsertSchema(); + stopwordsSetSchema.stopwords(stopwords); + + client.stopwords().upsert("common-words", stopwordsSetSchema); + } + + @Test + void testRetrieve() throws Exception { + helper.createTestStopwordsSet(); + + StopwordsSetRetrieveSchema result = this.client.stopwords("common-words").retrieve(); + + assertNotNull(result); + + StopwordsSetSchema set = result.getStopwords(); + + assertEquals("common-words", set.getId()); + assertEquals(3, set.getStopwords().size()); + assertEquals("and", set.getStopwords().get(0)); + assertEquals("the", set.getStopwords().get(1)); + assertEquals("of", set.getStopwords().get(2)); + } + + @Test + void testRetrieveAll() throws Exception { + helper.createTestStopwordsSet(); + + StopwordsSetsRetrieveAllSchema result = this.client.stopwords().retrieve(); + + assertNotNull(result); + assertEquals(1, result.getStopwords().size()); + + StopwordsSetSchema set = result.getStopwords().get(0); + + assertEquals("common-words", set.getId()); + assertEquals(3, set.getStopwords().size()); + assertEquals("and", set.getStopwords().get(0)); + assertEquals("the", set.getStopwords().get(1)); + assertEquals("of", set.getStopwords().get(2)); + } + + @Test + void testDelete() throws Exception { + helper.createTestStopwordsSet(); + + StopwordsSetSchema result = this.client.stopwords("common-words").delete(); + + assertNotNull(result); + assertEquals("common-words", result.getId()); + } +} diff --git a/src/test/java/org/typesense/api/SynonymSetTest.java b/src/test/java/org/typesense/api/SynonymSetTest.java new file mode 100644 index 0000000..ea17727 --- /dev/null +++ b/src/test/java/org/typesense/api/SynonymSetTest.java @@ -0,0 +1,93 @@ +package org.typesense.api; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.model.SynonymItemSchema; +import org.typesense.model.SynonymSetCreateSchema; +import org.typesense.model.SynonymSetSchema; +import org.typesense.model.SynonymSetDeleteSchema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class SynonymSetTest { + + private Client client; + private Helper helper; + private String synonymSetName; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + + if (!Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + + helper.teardown(); + helper.createTestCollection(); + synonymSetName = "test-synonym-set-" + System.currentTimeMillis(); + } + + @AfterEach + void tearDown() throws Exception { + try { + client.synonymSet(synonymSetName).delete(); + } catch (Exception e) { + } + helper.teardown(); + } + + @Test + void testRetrieve() throws Exception { + SynonymSetCreateSchema synonymSetData = helper.createTestSynonymSetData(); + client.synonymSets().upsert(synonymSetName, synonymSetData); + + SynonymSetCreateSchema result = client.synonymSet(synonymSetName).retrieve(); + + assertNotNull(result); + assertEquals(1, result.getItems().size()); + assertEquals("dummy", result.getItems().get(0).getId()); + assertEquals(Arrays.asList("foo", "bar", "baz"), result.getItems().get(0).getSynonyms()); + } + + @Test + void testUpdate() throws Exception { + SynonymSetCreateSchema originalData = helper.createTestSynonymSetData(); + client.synonymSets().upsert(synonymSetName, originalData); + + SynonymSetCreateSchema updatedData = new SynonymSetCreateSchema(); + List items = new ArrayList<>(); + SynonymItemSchema item = new SynonymItemSchema(); + item.setId("dummy"); + item.setSynonyms(Arrays.asList("foo", "bar", "baz", "qux")); + items.add(item); + updatedData.setItems(items); + + SynonymSetSchema result = client.synonymSet(synonymSetName).upsert(updatedData); + + assertEquals(synonymSetName, result.getName()); + assertEquals(1, result.getItems().size()); + assertEquals("dummy", result.getItems().get(0).getId()); + assertEquals(Arrays.asList("foo", "bar", "baz", "qux"), result.getItems().get(0).getSynonyms()); + } + + @Test + void testDelete() throws Exception { + SynonymSetCreateSchema synonymSetData = helper.createTestSynonymSetData(); + client.synonymSets().upsert(synonymSetName, synonymSetData); + + SynonymSetDeleteSchema result = client.synonymSet(synonymSetName).delete(); + + assertEquals(synonymSetName, result.getName()); + + assertThrows(Exception.class, () -> { + client.synonymSet(synonymSetName).retrieve(); + }); + } +} \ No newline at end of file diff --git a/src/test/java/org/typesense/api/SynonymSetsTest.java b/src/test/java/org/typesense/api/SynonymSetsTest.java new file mode 100644 index 0000000..430cfad --- /dev/null +++ b/src/test/java/org/typesense/api/SynonymSetsTest.java @@ -0,0 +1,80 @@ +package org.typesense.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.model.SynonymItemSchema; +import org.typesense.model.SynonymSetCreateSchema; +import org.typesense.model.SynonymSetSchema; + +import static org.junit.jupiter.api.Assertions.*; + +class SynonymSetsTest { + + private Client client; + private Helper helper; + private String synonymSetName; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + + if (!Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + + helper.teardown(); + helper.createTestCollection(); + synonymSetName = "test-synonym-set-" + System.currentTimeMillis(); + } + + @AfterEach + void tearDown() throws Exception { + try { + client.synonymSet(synonymSetName).delete(); + } catch (Exception e) { + } + helper.teardown(); + } + + @Test + void testUpsert() throws Exception { + SynonymSetCreateSchema synonymSetData = helper.createTestSynonymSetData(); + + SynonymSetSchema result = client.synonymSets().upsert(synonymSetName, synonymSetData); + + assertEquals(synonymSetName, result.getName()); + assertEquals(1, result.getItems().size()); + assertEquals("dummy", result.getItems().get(0).getId()); + assertEquals(Arrays.asList("foo", "bar", "baz"), result.getItems().get(0).getSynonyms()); + } + + @Test + void testRetrieve() throws Exception { + SynonymSetCreateSchema synonymSetData = helper.createTestSynonymSetData(); + client.synonymSets().upsert(synonymSetName, synonymSetData); + + SynonymSetSchema[] result = client.synonymSets().retrieve(); + + assertNotNull(result); + assertTrue(result.length >= 1); + + SynonymSetSchema foundSynonymSet = null; + for (SynonymSetSchema ss : result) { + if (synonymSetName.equals(ss.getName())) { + foundSynonymSet = ss; + break; + } + } + + assertNotNull(foundSynonymSet); + assertEquals(synonymSetName, foundSynonymSet.getName()); + assertEquals(1, foundSynonymSet.getItems().size()); + assertEquals("dummy", foundSynonymSet.getItems().get(0).getId()); + assertEquals(Arrays.asList("foo", "bar", "baz"), foundSynonymSet.getItems().get(0).getSynonyms()); + } +} diff --git a/src/test/java/org/typesense/api/SynonymsTest.java b/src/test/java/org/typesense/api/SynonymsTest.java index 57da150..13947d2 100644 --- a/src/test/java/org/typesense/api/SynonymsTest.java +++ b/src/test/java/org/typesense/api/SynonymsTest.java @@ -14,6 +14,11 @@ class SynonymsTest { void setUp() throws Exception { helper = new Helper(); client = helper.getClient(); + + if (Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + helper.teardown(); helper.createTestCollection(); helper.createTestSynonym(); @@ -49,4 +54,4 @@ void testRetrieveAll() throws Exception { void testDelete() throws Exception { System.out.println(this.client.collections("books").synonyms("coat-synonyms").delete()); } -} \ No newline at end of file +}