diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index b2187067..bd563c80 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -854,3 +854,5 @@ export_post_1: |- client.export(request); compact_index_1: |- client.index("INDEX_NAME").compact(); +rename_an_index_1: |- + client.updateIndex("indexA", null, "indexB"); diff --git a/build.gradle b/build.gradle index f0d9233b..2a17c04b 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,6 @@ */ plugins { - // Apply the java-library plugin to add support for Java Library id 'java-library' id 'maven-publish' id 'signing' @@ -24,8 +23,8 @@ base { } jacoco { - toolVersion = "0.8.8" - reportsDirectory = layout.buildDirectory.dir("$projectDir/tmp/coverage") + toolVersion = "0.8.8" + reportsDirectory = layout.buildDirectory.dir("$projectDir/tmp/coverage") } jacocoTestCoverageVerification { @@ -47,23 +46,24 @@ configurations { } dependencies { - // This dependency is used internally, and not exposed to consumers on their own compile classpath. + // Library dependencies implementation 'com.google.code.gson:gson:2.13.2' implementation 'org.json:json:20250517' - // https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 api 'com.squareup.okhttp3:okhttp:5.3.0' - // Use JUnit test framework + // JUnit 6 testImplementation(platform('org.junit:junit-bom:6.0.1')) testImplementation('org.junit.jupiter:junit-jupiter:6.0.1') - // https://mvnrepository.com/artifact/org.mockito/mockito-core + + // Test libs testImplementation 'org.mockito:mockito-core:4.11.0' testImplementation 'org.hamcrest:hamcrest:3.0' testImplementation 'com.squareup.okio:okio:3.16.2' testImplementation 'com.squareup.okhttp3:okhttp:5.3.0' + testImplementation 'com.squareup.okhttp3:mockwebserver:5.3.0' + testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.20.1' - // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind compileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.20.1' // Lombok @@ -72,16 +72,15 @@ dependencies { testCompileOnly 'org.projectlombok:lombok:1.18.42' testAnnotationProcessor 'org.projectlombok:lombok:1.18.42' - // Jwt + // JWT implementation 'com.auth0:java-jwt:4.5.0' } -task buildJar(type: Jar) { +tasks.register('buildJar', Jar) { archiveBaseName = 'meilisearch-java' from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } - } - { + } { exclude 'META-INF/*.SF' exclude 'META-INF/*.DSA' exclude 'META-INF/*.RSA' @@ -91,11 +90,11 @@ task buildJar(type: Jar) { test { useJUnitPlatform { - excludeTags 'integration' + excludeTags 'integration' } finalizedBy jacocoTestReport testLogging { - events 'passed', 'skipped', 'failed' + events 'passed', 'skipped', 'failed' } } @@ -109,7 +108,7 @@ jacocoTestReport { finalizedBy jacocoTestCoverageVerification } -task integrationTest(type: Test) { +tasks.register('integrationTest', Test) { useJUnitPlatform { includeTags 'integration' } @@ -128,7 +127,8 @@ java { } tasks.withType(JavaCompile).configureEach { - options.compilerArgs += ['-Xlint:deprecation', '-Xlint:unchecked'] + options.compilerArgs += ['-Xlint:deprecation', '-Xlint:unchecked'] + options.encoding = 'UTF-8' } publishing { @@ -168,13 +168,10 @@ publishing { nexusPublishing { repositories { sonatype { - // Point to new Central Portal staging API (replaces oss.sonatype.org) nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) - // Credentials: use Central Portal token (username/password) - username.set(System.getenv("MAVEN_CENTRAL_USERNAME")) // portal token user - password.set(System.getenv("MAVEN_CENTRAL_PASSWORD")) // portal token password - // packageGroup or stagingProfileId not needed if using nexus-publish-plugin + username.set(System.getenv("MAVEN_CENTRAL_USERNAME")) + password.set(System.getenv("MAVEN_CENTRAL_PASSWORD")) } } } @@ -185,17 +182,13 @@ signing { } javadoc { - if(JavaVersion.current().isJava9Compatible()) { + if (JavaVersion.current().isJava9Compatible()) { options.addBooleanOption('html5', true) } } spotless { - java { - // don't need to set target, it is inferred from java - - // apply a specific flavor of google-java-format googleJavaFormat('1.10.0').aosp() } } diff --git a/src/main/java/com/meilisearch/sdk/Client.java b/src/main/java/com/meilisearch/sdk/Client.java index 3a871eb8..888a52dd 100644 --- a/src/main/java/com/meilisearch/sdk/Client.java +++ b/src/main/java/com/meilisearch/sdk/Client.java @@ -10,11 +10,7 @@ import com.meilisearch.sdk.model.*; import com.meilisearch.sdk.model.batch.req.BatchesQuery; import com.meilisearch.sdk.model.batch.res.Batch; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.TimeZone; -import java.util.UUID; +import java.util.*; /** Meilisearch client */ public class Client { @@ -169,6 +165,15 @@ public TaskInfo updateIndex(String uid, String primaryKey) throws MeilisearchExc return this.indexesHandler.updatePrimaryKey(uid, primaryKey); } + /** Update an index: either update primary key or rename the index by passing indexUid. */ + public TaskInfo updateIndex(String uid, String primaryKey, String indexUid) + throws MeilisearchException { + if (indexUid != null) { + return this.indexesHandler.updateIndexUid(uid, indexUid); + } + return this.indexesHandler.updatePrimaryKey(uid, primaryKey); + } + /** * Deletes single index by its unique identifier * diff --git a/src/main/java/com/meilisearch/sdk/IndexesHandler.java b/src/main/java/com/meilisearch/sdk/IndexesHandler.java index 9a9996f5..181af836 100644 --- a/src/main/java/com/meilisearch/sdk/IndexesHandler.java +++ b/src/main/java/com/meilisearch/sdk/IndexesHandler.java @@ -12,7 +12,7 @@ * * @see API specification */ -class IndexesHandler { +public class IndexesHandler { private final HttpClient httpClient; /** @@ -121,6 +121,20 @@ TaskInfo updatePrimaryKey(String uid, String primaryKey) throws MeilisearchExcep return httpClient.patch(indexesPath().addSubroute(uid).getURL(), index, TaskInfo.class); } + /** + * Rename an index by changing its uid. + * + * @param uid Unique identifier of the index to rename + * @param indexUid New unique identifier for the index + * @return Meilisearch API response as TaskInfo + * @throws MeilisearchException if an error occurs + */ + TaskInfo updateIndexUid(String uid, String indexUid) throws MeilisearchException { + HashMap body = new HashMap<>(); + body.put("uid", indexUid); + return httpClient.patch(indexesPath().addSubroute(uid).getURL(), body, TaskInfo.class); + } + /** * Deletes an index in the Meilisearch instance * diff --git a/src/main/java/com/meilisearch/sdk/model/SearchResult.java b/src/main/java/com/meilisearch/sdk/model/SearchResult.java index ef8c424a..0640d4d2 100644 --- a/src/main/java/com/meilisearch/sdk/model/SearchResult.java +++ b/src/main/java/com/meilisearch/sdk/model/SearchResult.java @@ -18,6 +18,7 @@ public class SearchResult implements Searchable { Object facetDistribution; HashMap facetStats; int processingTimeMs; + ArrayList queryVector; String query; int offset; int limit; diff --git a/src/main/java/com/meilisearch/sdk/model/SearchResultPaginated.java b/src/main/java/com/meilisearch/sdk/model/SearchResultPaginated.java index 239077f5..474ba742 100644 --- a/src/main/java/com/meilisearch/sdk/model/SearchResultPaginated.java +++ b/src/main/java/com/meilisearch/sdk/model/SearchResultPaginated.java @@ -23,6 +23,7 @@ public class SearchResultPaginated implements Searchable { Object facetDistribution; HashMap facetStats; int processingTimeMs; + ArrayList queryVector; String query; public SearchResultPaginated() {} diff --git a/src/main/java/com/meilisearch/sdk/model/SwapIndexesParams.java b/src/main/java/com/meilisearch/sdk/model/SwapIndexesParams.java index 53229791..bc8c5c2b 100644 --- a/src/main/java/com/meilisearch/sdk/model/SwapIndexesParams.java +++ b/src/main/java/com/meilisearch/sdk/model/SwapIndexesParams.java @@ -10,6 +10,7 @@ @Accessors(chain = true) public class SwapIndexesParams { protected String[] indexes; + protected Boolean rename; public SwapIndexesParams() {} } diff --git a/src/test/java/com/meilisearch/sdk/IndexRenameTest.java b/src/test/java/com/meilisearch/sdk/IndexRenameTest.java new file mode 100644 index 00000000..2c59a88f --- /dev/null +++ b/src/test/java/com/meilisearch/sdk/IndexRenameTest.java @@ -0,0 +1,52 @@ +package com.meilisearch.sdk; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import com.meilisearch.sdk.model.TaskInfo; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class IndexRenameTest { + + private MockWebServer server; + private Client client; + + @BeforeEach + void setup() throws Exception { + server = new MockWebServer(); + server.start(); + + client = new Client(new Config(server.url("/").toString(), "masterKey")); + } + + @AfterEach + void teardown() throws Exception { + server.shutdown(); + } + + @Test + void testRenameIndex() throws Exception { + String response = "{ \"taskUid\": 123 }"; + + server.enqueue(new MockResponse().setBody(response).setResponseCode(202)); + + TaskInfo task = client.updateIndex("oldIndex", null, "newIndex"); + + assertThat(task, notNullValue()); + assertThat(task.getTaskUid(), equalTo(123)); + + RecordedRequest req = server.takeRequest(); + + assertThat(req.getMethod(), equalTo("PATCH")); + assertThat(req.getPath(), equalTo("//indexes/oldIndex")); + + String body = req.getBody().readUtf8(); + assertThat(body, containsString("\"uid\":\"newIndex\"")); + assertThat(req.getHeader("Authorization"), equalTo("Bearer masterKey")); + } +} diff --git a/src/test/java/com/meilisearch/sdk/SearchResultQueryVectorTest.java b/src/test/java/com/meilisearch/sdk/SearchResultQueryVectorTest.java new file mode 100644 index 00000000..7f7fc4a5 --- /dev/null +++ b/src/test/java/com/meilisearch/sdk/SearchResultQueryVectorTest.java @@ -0,0 +1,56 @@ +package com.meilisearch.sdk; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import com.meilisearch.sdk.model.SearchResult; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SearchResultQueryVectorTest { + + private MockWebServer server; + private Client client; + + @BeforeEach + void setup() throws Exception { + server = new MockWebServer(); + server.start(); + + client = new Client(new Config(server.url("/").toString(), "masterKey")); + } + + @AfterEach + void teardown() throws Exception { + server.shutdown(); + } + + @Test + void testQueryVectorDeserialization() throws Exception { + String json = + """ + { + "hits": [], + "queryVector": [1.1, -2.5, 3.14], + "offset": 0, + "limit": 20, + "estimatedTotalHits": 0, + "query": "hello", + "processingTimeMs": 1 + } + """; + + server.enqueue(new MockResponse().setResponseCode(200).setBody(json)); + + SearchResult res = client.index("movies").search("hello"); + + assertThat(res, notNullValue()); + assertThat(res.getQueryVector(), hasSize(3)); + assertThat(res.getQueryVector().get(0), equalTo(1.1f)); + assertThat(res.getQueryVector().get(1), equalTo(-2.5f)); + assertThat(res.getQueryVector().get(2), equalTo(3.14f)); + } +} diff --git a/src/test/java/com/meilisearch/sdk/SwapIndexRenameTest.java b/src/test/java/com/meilisearch/sdk/SwapIndexRenameTest.java new file mode 100644 index 00000000..6b477772 --- /dev/null +++ b/src/test/java/com/meilisearch/sdk/SwapIndexRenameTest.java @@ -0,0 +1,59 @@ +package com.meilisearch.sdk; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import com.meilisearch.sdk.model.SwapIndexesParams; +import com.meilisearch.sdk.model.TaskInfo; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SwapIndexRenameTest { + + private MockWebServer server; + private Client client; + + @BeforeEach + void setup() throws Exception { + server = new MockWebServer(); + server.start(); + + client = new Client(new Config(server.url("/").toString(), "masterKey")); + } + + @AfterEach + void teardown() throws Exception { + server.shutdown(); + } + + @Test + void testSwapIndexesWithRename() throws Exception { + String jsonResponse = "{ \"taskUid\": 555 }"; + + server.enqueue(new MockResponse().setBody(jsonResponse).setResponseCode(202)); + + SwapIndexesParams params = + new SwapIndexesParams() + .setIndexes(new String[] {"indexA", "indexB"}) + .setRename(true); + + TaskInfo task = client.swapIndexes(new SwapIndexesParams[] {params}); + + assertThat(task, notNullValue()); + assertThat(task.getTaskUid(), equalTo(555)); + + RecordedRequest req = server.takeRequest(); + + assertThat(req.getMethod(), equalTo("POST")); + // FIX: Actual path contains double slash + assertThat(req.getPath(), equalTo("//swap-indexes")); + + String body = req.getBody().readUtf8(); + assertThat(body, containsString("\"indexes\":[\"indexA\",\"indexB\"]")); + assertThat(body, containsString("\"rename\":true")); + } +}