Skip to content

Commit f0fa762

Browse files
Add queryVector to search responses and support index renaming (#704)
* feat: Add queryVector to search responses and support index renaming * nit --------- Co-authored-by: Clémentine <[email protected]>
1 parent 6c666c1 commit f0fa762

File tree

4 files changed

+154
-0
lines changed

4 files changed

+154
-0
lines changed

.code-samples.meilisearch.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ update_an_index_1: |-
9494
.execute()
9595
.await
9696
.unwrap();
97+
rename_an_index_1: |-
98+
curl \
99+
-X PATCH 'MEILISEARCH_URL/indexes/INDEX_A' \
100+
-H 'Content-Type: application/json' \
101+
--data-binary '{ "uid": "INDEX_B" }'
97102
delete_an_index_1: |-
98103
client.index("movies")
99104
.delete()

src/client.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub struct Client<Http: HttpClient = DefaultHttpClient> {
2727
#[derive(Debug, Clone, Serialize, Deserialize)]
2828
pub struct SwapIndexes {
2929
pub indexes: (String, String),
30+
#[serde(skip_serializing_if = "Option::is_none")]
31+
pub rename: Option<bool>,
3032
}
3133

3234
#[cfg(feature = "reqwest")]
@@ -532,6 +534,7 @@ impl<Http: HttpClient> Client<Http> {
532534
/// "swap_index_1".to_string(),
533535
/// "swap_index_2".to_string(),
534536
/// ),
537+
/// rename: None,
535538
/// }])
536539
/// .await
537540
/// .unwrap();
@@ -1249,6 +1252,7 @@ mod tests {
12491252
"test_swapping_two_indexes_1".to_string(),
12501253
"test_swapping_two_indexes_2".to_string(),
12511254
),
1255+
rename: None,
12521256
}])
12531257
.await
12541258
.unwrap();
@@ -1351,6 +1355,39 @@ mod tests {
13511355
assert_eq!(tasks.limit, 20);
13521356
}
13531357

1358+
#[meilisearch_test]
1359+
async fn test_rename_index_via_swap(client: Client, name: String) -> Result<(), Error> {
1360+
let from = format!("{name}_from");
1361+
let to = format!("{name}_to");
1362+
1363+
client
1364+
.create_index(&from, None)
1365+
.await?
1366+
.wait_for_completion(&client, None, None)
1367+
.await?;
1368+
1369+
let task = client
1370+
.swap_indexes([&SwapIndexes {
1371+
indexes: (from.clone(), to.clone()),
1372+
rename: Some(true),
1373+
}])
1374+
.await?;
1375+
task.wait_for_completion(&client, None, None).await?;
1376+
1377+
let new_index = client.get_index(&to).await?;
1378+
assert_eq!(new_index.uid, to);
1379+
// Optional: old uid should no longer resolve
1380+
assert!(client.get_raw_index(&from).await.is_err());
1381+
1382+
new_index
1383+
.delete()
1384+
.await?
1385+
.wait_for_completion(&client, None, None)
1386+
.await?;
1387+
1388+
Ok(())
1389+
}
1390+
13541391
#[meilisearch_test]
13551392
async fn test_get_tasks_with_params(client: Client) {
13561393
let query = TasksSearchQuery::new(&client);

src/indexes.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,9 @@ pub struct IndexUpdater<'a, Http: HttpClient> {
17731773
pub client: &'a Client<Http>,
17741774
#[serde(skip_serializing)]
17751775
pub uid: String,
1776+
/// New uid to rename the index to
1777+
#[serde(rename = "uid", skip_serializing_if = "Option::is_none")]
1778+
pub new_uid: Option<String>,
17761779
pub primary_key: Option<String>,
17771780
}
17781781

@@ -1782,6 +1785,7 @@ impl<'a, Http: HttpClient> IndexUpdater<'a, Http> {
17821785
client,
17831786
primary_key: None,
17841787
uid: uid.as_ref().to_string(),
1788+
new_uid: None,
17851789
}
17861790
}
17871791
/// Define the new `primary_key` to set on the [Index].
@@ -1829,6 +1833,17 @@ impl<'a, Http: HttpClient> IndexUpdater<'a, Http> {
18291833
self
18301834
}
18311835

1836+
/// Define a new `uid` to rename the index.
1837+
pub fn with_uid(&mut self, new_uid: impl AsRef<str>) -> &mut IndexUpdater<'a, Http> {
1838+
self.new_uid = Some(new_uid.as_ref().to_string());
1839+
self
1840+
}
1841+
1842+
/// Alias for `with_uid` with clearer intent.
1843+
pub fn with_new_uid(&mut self, new_uid: impl AsRef<str>) -> &mut IndexUpdater<'a, Http> {
1844+
self.with_uid(new_uid)
1845+
}
1846+
18321847
/// Execute the update of an [Index] using the [`IndexUpdater`].
18331848
///
18341849
/// # Example
@@ -2223,6 +2238,52 @@ mod tests {
22232238
Ok(())
22242239
}
22252240

2241+
#[meilisearch_test]
2242+
async fn test_rename_index_via_update(client: Client, name: String) -> Result<(), Error> {
2243+
let from = format!("{name}_from");
2244+
let to = format!("{name}_to");
2245+
2246+
// Create source index
2247+
client
2248+
.create_index(&from, None)
2249+
.await?
2250+
.wait_for_completion(&client, None, None)
2251+
.await?;
2252+
2253+
// Rename using index update
2254+
IndexUpdater::new(&from, &client)
2255+
.with_uid(&to)
2256+
.execute()
2257+
.await?
2258+
.wait_for_completion(&client, None, None)
2259+
.await?;
2260+
2261+
// New index should exist
2262+
let new_index = client.get_index(&to).await?;
2263+
assert_eq!(new_index.uid, to);
2264+
2265+
// Old index should no longer exist
2266+
let old_index = client.get_index(&from).await;
2267+
assert!(old_index.is_err(), "old uid still resolves after rename");
2268+
2269+
// cleanup
2270+
new_index
2271+
.delete()
2272+
.await?
2273+
.wait_for_completion(&client, None, None)
2274+
.await?;
2275+
2276+
// defensive cleanup if rename semantics change
2277+
if let Ok(idx) = client.get_index(&from).await {
2278+
idx.delete()
2279+
.await?
2280+
.wait_for_completion(&client, None, None)
2281+
.await?;
2282+
}
2283+
2284+
Ok(())
2285+
}
2286+
22262287
#[meilisearch_test]
22272288
async fn test_add_documents_ndjson(client: Client, index: Index) -> Result<(), Error> {
22282289
let ndjson = r#"{ "id": 1, "body": "doggo" }{ "id": 2, "body": "catto" }"#.as_bytes();

src/search.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ pub struct SearchResults<T> {
121121
pub query: String,
122122
/// Index uid on which the search was made.
123123
pub index_uid: Option<String>,
124+
/// The query vector returned when `retrieveVectors` is enabled.
125+
/// Accept multiple possible field names to be forward/backward compatible with server variations.
126+
#[serde(
127+
rename = "queryVector",
128+
alias = "query_vector",
129+
alias = "queryEmbedding",
130+
alias = "query_embedding",
131+
alias = "vector",
132+
skip_serializing_if = "Option::is_none"
133+
)]
134+
pub query_vector: Option<Vec<f32>>,
124135
}
125136

126137
fn serialize_attributes_to_crop_with_wildcard<S: Serializer>(
@@ -2017,6 +2028,46 @@ pub(crate) mod tests {
20172028
Ok(())
20182029
}
20192030

2031+
#[meilisearch_test]
2032+
async fn test_query_vector_in_response(client: Client, index: Index) -> Result<(), Error> {
2033+
setup_embedder(&client, &index).await?;
2034+
setup_test_index(&client, &index).await?;
2035+
2036+
let mut query = SearchQuery::new(&index);
2037+
let qv = vectorize(false, 0);
2038+
query
2039+
.with_hybrid("default", 1.0)
2040+
.with_vector(&qv)
2041+
.with_retrieve_vectors(true);
2042+
2043+
let results: SearchResults<Document> = index.execute_query(&query).await?;
2044+
2045+
if std::env::var("MSDK_DEBUG_RAW_SEARCH").ok().as_deref() == Some("1")
2046+
&& results.query_vector.is_none()
2047+
{
2048+
use crate::request::Method;
2049+
let url = format!("{}/indexes/{}/search", index.client.get_host(), index.uid);
2050+
let raw: serde_json::Value = index
2051+
.client
2052+
.http_client
2053+
.request::<(), &SearchQuery<_>, serde_json::Value>(
2054+
&url,
2055+
Method::Post {
2056+
body: &query,
2057+
query: (),
2058+
},
2059+
200,
2060+
)
2061+
.await
2062+
.unwrap();
2063+
eprintln!("DEBUG raw search response: {}", raw);
2064+
}
2065+
2066+
assert!(results.query_vector.is_some());
2067+
assert_eq!(results.query_vector.as_ref().unwrap().len(), 11);
2068+
Ok(())
2069+
}
2070+
20202071
#[meilisearch_test]
20212072
async fn test_hybrid(client: Client, index: Index) -> Result<(), Error> {
20222073
setup_embedder(&client, &index).await?;

0 commit comments

Comments
 (0)