diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 0b0af624..48c194a2 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -94,6 +94,11 @@ update_an_index_1: |- .execute() .await .unwrap(); +rename_an_index_1: |- + curl \ + -X PATCH 'MEILISEARCH_URL/indexes/INDEX_A' \ + -H 'Content-Type: application/json' \ + --data-binary '{ "uid": "INDEX_B" }' delete_an_index_1: |- client.index("movies") .delete() diff --git a/src/client.rs b/src/client.rs index d6c264e5..f9dfe98c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -27,6 +27,8 @@ pub struct Client { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SwapIndexes { pub indexes: (String, String), + #[serde(skip_serializing_if = "Option::is_none")] + pub rename: Option, } #[cfg(feature = "reqwest")] @@ -532,6 +534,7 @@ impl Client { /// "swap_index_1".to_string(), /// "swap_index_2".to_string(), /// ), + /// rename: None, /// }]) /// .await /// .unwrap(); @@ -1249,6 +1252,7 @@ mod tests { "test_swapping_two_indexes_1".to_string(), "test_swapping_two_indexes_2".to_string(), ), + rename: None, }]) .await .unwrap(); @@ -1351,6 +1355,39 @@ mod tests { assert_eq!(tasks.limit, 20); } + #[meilisearch_test] + async fn test_rename_index_via_swap(client: Client, name: String) -> Result<(), Error> { + let from = format!("{name}_from"); + let to = format!("{name}_to"); + + client + .create_index(&from, None) + .await? + .wait_for_completion(&client, None, None) + .await?; + + let task = client + .swap_indexes([&SwapIndexes { + indexes: (from.clone(), to.clone()), + rename: Some(true), + }]) + .await?; + task.wait_for_completion(&client, None, None).await?; + + let new_index = client.get_index(&to).await?; + assert_eq!(new_index.uid, to); + // Optional: old uid should no longer resolve + assert!(client.get_raw_index(&from).await.is_err()); + + new_index + .delete() + .await? + .wait_for_completion(&client, None, None) + .await?; + + Ok(()) + } + #[meilisearch_test] async fn test_get_tasks_with_params(client: Client) { let query = TasksSearchQuery::new(&client); diff --git a/src/indexes.rs b/src/indexes.rs index b072f3d3..febf25fa 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -1773,6 +1773,9 @@ pub struct IndexUpdater<'a, Http: HttpClient> { pub client: &'a Client, #[serde(skip_serializing)] pub uid: String, + /// New uid to rename the index to + #[serde(rename = "uid", skip_serializing_if = "Option::is_none")] + pub new_uid: Option, pub primary_key: Option, } @@ -1782,6 +1785,7 @@ impl<'a, Http: HttpClient> IndexUpdater<'a, Http> { client, primary_key: None, uid: uid.as_ref().to_string(), + new_uid: None, } } /// Define the new `primary_key` to set on the [Index]. @@ -1829,6 +1833,17 @@ impl<'a, Http: HttpClient> IndexUpdater<'a, Http> { self } + /// Define a new `uid` to rename the index. + pub fn with_uid(&mut self, new_uid: impl AsRef) -> &mut IndexUpdater<'a, Http> { + self.new_uid = Some(new_uid.as_ref().to_string()); + self + } + + /// Alias for `with_uid` with clearer intent. + pub fn with_new_uid(&mut self, new_uid: impl AsRef) -> &mut IndexUpdater<'a, Http> { + self.with_uid(new_uid) + } + /// Execute the update of an [Index] using the [`IndexUpdater`]. /// /// # Example @@ -2223,6 +2238,52 @@ mod tests { Ok(()) } + #[meilisearch_test] + async fn test_rename_index_via_update(client: Client, name: String) -> Result<(), Error> { + let from = format!("{name}_from"); + let to = format!("{name}_to"); + + // Create source index + client + .create_index(&from, None) + .await? + .wait_for_completion(&client, None, None) + .await?; + + // Rename using index update + IndexUpdater::new(&from, &client) + .with_uid(&to) + .execute() + .await? + .wait_for_completion(&client, None, None) + .await?; + + // New index should exist + let new_index = client.get_index(&to).await?; + assert_eq!(new_index.uid, to); + + // Old index should no longer exist + let old_index = client.get_index(&from).await; + assert!(old_index.is_err(), "old uid still resolves after rename"); + + // cleanup + new_index + .delete() + .await? + .wait_for_completion(&client, None, None) + .await?; + + // defensive cleanup if rename semantics change + if let Ok(idx) = client.get_index(&from).await { + idx.delete() + .await? + .wait_for_completion(&client, None, None) + .await?; + } + + Ok(()) + } + #[meilisearch_test] async fn test_add_documents_ndjson(client: Client, index: Index) -> Result<(), Error> { let ndjson = r#"{ "id": 1, "body": "doggo" }{ "id": 2, "body": "catto" }"#.as_bytes(); diff --git a/src/search.rs b/src/search.rs index 1cae8be5..084a0d3f 100644 --- a/src/search.rs +++ b/src/search.rs @@ -121,6 +121,17 @@ pub struct SearchResults { pub query: String, /// Index uid on which the search was made. pub index_uid: Option, + /// The query vector returned when `retrieveVectors` is enabled. + /// Accept multiple possible field names to be forward/backward compatible with server variations. + #[serde( + rename = "queryVector", + alias = "query_vector", + alias = "queryEmbedding", + alias = "query_embedding", + alias = "vector", + skip_serializing_if = "Option::is_none" + )] + pub query_vector: Option>, } fn serialize_attributes_to_crop_with_wildcard( @@ -2017,6 +2028,46 @@ pub(crate) mod tests { Ok(()) } + #[meilisearch_test] + async fn test_query_vector_in_response(client: Client, index: Index) -> Result<(), Error> { + setup_embedder(&client, &index).await?; + setup_test_index(&client, &index).await?; + + let mut query = SearchQuery::new(&index); + let qv = vectorize(false, 0); + query + .with_hybrid("default", 1.0) + .with_vector(&qv) + .with_retrieve_vectors(true); + + let results: SearchResults = index.execute_query(&query).await?; + + if std::env::var("MSDK_DEBUG_RAW_SEARCH").ok().as_deref() == Some("1") + && results.query_vector.is_none() + { + use crate::request::Method; + let url = format!("{}/indexes/{}/search", index.client.get_host(), index.uid); + let raw: serde_json::Value = index + .client + .http_client + .request::<(), &SearchQuery<_>, serde_json::Value>( + &url, + Method::Post { + body: &query, + query: (), + }, + 200, + ) + .await + .unwrap(); + eprintln!("DEBUG raw search response: {}", raw); + } + + assert!(results.query_vector.is_some()); + assert_eq!(results.query_vector.as_ref().unwrap().len(), 11); + Ok(()) + } + #[meilisearch_test] async fn test_hybrid(client: Client, index: Index) -> Result<(), Error> { setup_embedder(&client, &index).await?;