Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .code-samples.meilisearch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
37 changes: 37 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub struct Client<Http: HttpClient = DefaultHttpClient> {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwapIndexes {
pub indexes: (String, String),
#[serde(skip_serializing_if = "Option::is_none")]
pub rename: Option<bool>,
}

#[cfg(feature = "reqwest")]
Expand Down Expand Up @@ -532,6 +534,7 @@ impl<Http: HttpClient> Client<Http> {
/// "swap_index_1".to_string(),
/// "swap_index_2".to_string(),
/// ),
/// rename: None,
/// }])
/// .await
/// .unwrap();
Expand Down Expand Up @@ -1249,6 +1252,7 @@ mod tests {
"test_swapping_two_indexes_1".to_string(),
"test_swapping_two_indexes_2".to_string(),
),
rename: None,
}])
.await
.unwrap();
Expand Down Expand Up @@ -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);
Expand Down
61 changes: 61 additions & 0 deletions src/indexes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,9 @@ pub struct IndexUpdater<'a, Http: HttpClient> {
pub client: &'a Client<Http>,
#[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<String>,
pub primary_key: Option<String>,
}

Expand All @@ -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].
Expand Down Expand Up @@ -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<str>) -> &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<str>) -> &mut IndexUpdater<'a, Http> {
self.with_uid(new_uid)
}

/// Execute the update of an [Index] using the [`IndexUpdater`].
///
/// # Example
Expand Down Expand Up @@ -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();
Expand Down
51 changes: 51 additions & 0 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ pub struct SearchResults<T> {
pub query: String,
/// Index uid on which the search was made.
pub index_uid: Option<String>,
/// 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<Vec<f32>>,
}

fn serialize_attributes_to_crop_with_wildcard<S: Serializer>(
Expand Down Expand Up @@ -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<Document> = 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?;
Expand Down