mochow_sdk_rust/mochow/api/
row.rs

1/*
2 * Copyright 2024 Baidu, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 * either express or implied. See the License for the specific language governing permissions
12 * and limitations under the License.
13 */
14
15use derive_builder::Builder;
16use serde::{Deserialize, Serialize};
17
18use crate::mochow::{client::IntoRequest, config::ClientConfiguration};
19
20use super::ReadConsistency;
21
22/**
23 * insert row args response with [InsertRowsResponse]
24 * you can use serde_json::Value as T or any struct that implements Serialize
25 */
26
27#[derive(Debug, Clone, Builder, Serialize)]
28pub struct InsertRowArgs<T> {
29    #[builder(setter(into))]
30    pub database: String,
31    #[builder(setter(into))]
32    pub table: String,
33    /// The inserted dataset supports a maximum of 1000 entries per batch
34    #[builder(setter(into))]
35    pub rows: Vec<T>,
36}
37
38#[derive(Debug, Clone, Deserialize)]
39pub struct InsertRowsResponse {
40    pub code: i32,
41    pub msg: String,
42    /// Number of successfully written records
43    #[serde(rename = "affectedCount")]
44    pub affected_count: i32,
45}
46
47/**
48 * upsert row args response with [UpsertRowsResponse]
49 * you can use serde_json::Value as T or any struct that implements Serialize
50 */
51
52#[derive(Debug, Clone, Builder, Serialize)]
53pub struct UpsertRowArgs<T> {
54    #[builder(setter(into))]
55    pub database: String,
56    #[builder(setter(into))]
57    pub table: String,
58    /// The inserted dataset supports a maximum of 1000 entries per batch
59    #[builder(setter(into))]
60    pub rows: Vec<T>,
61}
62
63#[derive(Debug, Clone, Deserialize)]
64pub struct UpsertRowsResponse {
65    pub code: i32,
66    pub msg: String,
67    /// Number of successfully written records
68    #[serde(rename = "affectedCount")]
69    pub affected_count: i32,
70}
71
72/**
73 * update row args response with [crate::mochow::api::CommonResponse]
74 */
75
76#[derive(Debug, Clone, Builder, Serialize)]
77pub struct UpdateRowArgs {
78    #[builder(setter(into))]
79    pub database: String,
80    #[builder(setter(into))]
81    pub table: String,
82
83    /// the primary key value of the record to be updated
84    #[builder(setter(into))]
85    #[serde(rename = "primaryKey")]
86    pub primary_key: serde_json::Value,
87
88    /// the partition value of the record to be updated. If the partition key and primary key of the table are the same key, there is no need to fill in the partition key value. The partition key value will only take effect if there is a primary key value
89    #[builder(default, setter(strip_option))]
90    #[serde(rename = "partitionKey", skip_serializing_if = "Option::is_none")]
91    pub partition_ey: Option<serde_json::Value>,
92
93    /// list of fields to be updated and their new values. Updating primary keys, partition keys, and vector fields is not allowed
94    #[builder(setter(into))]
95    pub update: serde_json::Value,
96}
97
98/**
99 * delete row args response with [crate::mochow::api::CommonResponse]
100 */
101
102#[derive(Debug, Clone, Builder, Serialize)]
103pub struct DeleteRowArgs {
104    #[builder(setter(into))]
105    pub database: String,
106    #[builder(setter(into))]
107    pub table: String,
108
109    #[builder(default, setter(strip_option))]
110    #[serde(rename = "primaryKey", skip_serializing_if = "Option::is_none")]
111    pub primary_key: Option<serde_json::Value>,
112
113    #[builder(default, setter(strip_option))]
114    #[serde(rename = "partitionKey", skip_serializing_if = "Option::is_none")]
115    pub partition_ey: Option<serde_json::Value>,
116
117    /// The syntax of the Filter expression is designed based on the WHERE clause syntax of SQL,
118    /// [syntax](https://cloud.baidu.com/doc/VDB/s/mlty5lzzb)
119    #[builder(default, setter(into, strip_option))]
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub filter: Option<String>,
122}
123
124/**
125 * query row args response with [QueryRowsResponse]
126 * query single row by primary key
127 */
128#[derive(Debug, Clone, Builder, Serialize)]
129pub struct QueryRowArgs {
130    #[builder(setter(into))]
131    pub database: String,
132    #[builder(setter(into))]
133    pub table: String,
134
135    #[builder(setter(into))]
136    #[serde(rename = "primaryKey")]
137    pub primary_key: serde_json::Value,
138
139    #[builder(default, setter(strip_option))]
140    #[serde(rename = "partitionKey", skip_serializing_if = "Option::is_none")]
141    pub partition_key: Option<serde_json::Value>,
142
143    /// projection field list, default to empty. When empty, the query result returns all scalar fields by default
144    #[builder(default, setter(into, strip_option))]
145    #[serde(rename = "projections", skip_serializing_if = "Option::is_none")]
146    pub projections: Option<Vec<String>>,
147
148    /// whether to return the vector field values in the query result record
149    #[builder(default, setter(strip_option))]
150    #[serde(rename = "retrieveVector", skip_serializing_if = "Option::is_none")]
151    pub retrieve_vector: Option<bool>,
152
153    /// consistency level of query request,
154    /// EVENT (default): final consistency,
155    /// STRONG: strong Consistency
156    #[builder(default, setter(into, strip_option))]
157    #[serde(rename = "readConsistency", skip_serializing_if = "Option::is_none")]
158    pub read_consistency: Option<ReadConsistency>,
159}
160
161/// response of query row, you can use serde_json::Value as T or any struct that implements Deserialize
162#[derive(Debug, Clone, Deserialize)]
163pub struct QueryRowsResponse<T> {
164    pub code: i32,
165    pub msg: String,
166    pub row: T,
167}
168
169/**
170 * search rows args response with [SearchRowsResponse]
171 * basing ann search of vector fields, support filter by scalar fields
172 */
173#[derive(Debug, Clone, Builder, Serialize)]
174pub struct SearchRowsArgs {
175    #[builder(setter(into))]
176    pub database: String,
177    #[builder(setter(into))]
178    pub table: String,
179
180    /// query parameter
181    #[builder(setter(into))]
182    pub anns: AnnsSearchParams,
183
184    #[builder(default, setter(strip_option))]
185    #[serde(rename = "partitionKey", skip_serializing_if = "Option::is_none")]
186    pub partition_ey: Option<serde_json::Value>,
187
188    #[builder(default, setter(into, strip_option))]
189    #[serde(rename = "projections", skip_serializing_if = "Option::is_none")]
190    pub projections: Option<Vec<String>>,
191
192    #[builder(default, setter(strip_option))]
193    #[serde(rename = "retrieveVector", skip_serializing_if = "Option::is_none")]
194    pub retrieve_vector: Option<bool>,
195
196    #[builder(default, setter(into, strip_option))]
197    #[serde(rename = "readConsistency", skip_serializing_if = "Option::is_none")]
198    pub read_consistency: Option<String>,
199}
200
201#[derive(Debug, Clone, Builder, Serialize)]
202pub struct AnnsSearchParams {
203    #[builder(setter(into))]
204    #[serde(rename = "vectorField")]
205    pub vector_field: String,
206
207    /// target vector
208    #[builder(setter(into))]
209    #[serde(rename = "vectorFloats")]
210    pub vector_floats: Vec<f64>,
211
212    #[builder(setter(into))]
213    pub params: VectorSearchParams,
214
215    /// SQL [syntax](https://cloud.baidu.com/doc/VDB/s/mlty5lzzb)
216    #[builder(default, setter(into, strip_option))]
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub filter: Option<String>,
219}
220
221#[derive(Debug, Clone, Serialize)]
222#[serde(untagged)]
223pub enum VectorSearchParams {
224    FLAT(FLATSearchParams),
225    HNSW(HNSWSearchParams),
226    HNSWPQ(HNSWPQSearchParams),
227    PUCK(PUCKSearchParams),
228}
229
230#[derive(Debug, Clone, Builder, Serialize)]
231pub struct HNSWSearchParams {
232    /// the size of the dynamic candidate list in the HNSW algorithm retrieval process
233    #[builder(setter(into))]
234    pub ef: u32,
235    #[builder(default = "50", setter(into))]
236    pub limit: u32,
237
238    /// the farthest distance in the range retrieval scenario
239    #[builder(default, setter(into, strip_option))]
240    #[serde(rename = "distanceFar", skip_serializing_if = "Option::is_none")]
241    pub distance_far: Option<f64>,
242
243    /// the nearest distance in the range retrieval scenario
244    #[builder(default, setter(into, strip_option))]
245    #[serde(rename = "distanceNear", skip_serializing_if = "Option::is_none")]
246    pub distance_near: Option<f64>,
247
248    #[builder(default, setter(into))]
249    pub pruning: bool,
250}
251
252#[derive(Debug, Clone, Builder, Serialize)]
253pub struct HNSWPQSearchParams {
254    /// the size of the dynamic candidate list in the HNSWPQ algorithm retrieval process
255    #[builder(setter(into))]
256    pub ef: u32,
257    #[builder(default = "50", setter(into))]
258    pub limit: u32,
259
260    /// the farthest distance in the range retrieval scenario
261    #[builder(default, setter(into, strip_option))]
262    #[serde(rename = "distanceFar", skip_serializing_if = "Option::is_none")]
263    pub distance_far: Option<f64>,
264
265    /// the nearest distance in the range retrieval scenario
266    #[builder(default, setter(into, strip_option))]
267    #[serde(rename = "distanceNear", skip_serializing_if = "Option::is_none")]
268    pub distance_near: Option<f64>,
269}
270
271#[derive(Debug, Clone, Builder, Serialize)]
272pub struct PUCKSearchParams {
273    /// the size of the coarse clustering center candidate set in the PUCK algorithm retrieval process
274    #[builder(setter(into))]
275    #[serde(rename = "searchCoarseCount")]
276    pub search_coarse_count: u32,
277    #[builder(default = "50", setter(into))]
278    pub limit: u32,
279    #[builder(default, setter(into, strip_option))]
280    #[serde(rename = "distanceFar", skip_serializing_if = "Option::is_none")]
281    pub distance_far: Option<f64>,
282    #[builder(default, setter(into, strip_option))]
283    #[serde(rename = "distanceNear", skip_serializing_if = "Option::is_none")]
284    pub distance_near: Option<f64>,
285}
286
287#[derive(Debug, Clone, Builder, Serialize)]
288pub struct FLATSearchParams {
289    #[builder(default = "50", setter(into))]
290    pub limit: u32,
291
292    /// the farthest distance in the range retrieval scenario
293    #[builder(default, setter(into, strip_option))]
294    #[serde(rename = "distanceFar", skip_serializing_if = "Option::is_none")]
295    pub distance_far: Option<f64>,
296
297    /// the nearest distance in the range retrieval scenario
298    #[builder(default, setter(into, strip_option))]
299    #[serde(rename = "distanceNear", skip_serializing_if = "Option::is_none")]
300    pub distance_near: Option<f64>,
301}
302
303/// response of search row, you can use serde_json::Value as T or any struct that implements Deserialize
304#[derive(Debug, Clone, Deserialize)]
305pub struct SearchRowsResponse<T> {
306    pub code: i32,
307    pub msg: String,
308    pub rows: Vec<RowResult<T>>,
309}
310
311#[derive(Debug, Clone, Deserialize)]
312pub struct RowResult<T> {
313    /// record result
314    pub row: T,
315    /// the distance of this record from the target vector
316    pub distance: f64,
317    /// the higher the score, the more similar it is to the target vector
318    pub score: f64,
319}
320
321/**
322 * select rows args response with [SelectRowsResponse]
323 * filter records by scalar fields
324 */
325#[derive(Debug, Clone, Builder, Serialize)]
326pub struct SelectRowsArgs {
327    #[builder(setter(into))]
328    pub database: String,
329    #[builder(setter(into))]
330    pub table: String,
331
332    /// SQL [syntax](https://cloud.baidu.com/doc/VDB/s/mlty5lzzb)
333    #[builder(default, setter(into, strip_option))]
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub filter: Option<String>,
336
337    /// starting point for pagination of queries, default from the first eligible record
338    #[builder(default, setter(strip_option))]
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub marker: Option<serde_json::Value>,
341
342    #[builder(default, setter(into, strip_option))]
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub limit: Option<u32>,
345
346    #[builder(default, setter(into, strip_option))]
347    #[serde(rename = "projections", skip_serializing_if = "Option::is_none")]
348    pub projections: Option<Vec<String>>,
349
350    #[builder(default, setter(into, strip_option))]
351    #[serde(rename = "readConsistency", skip_serializing_if = "Option::is_none")]
352    pub read_consistency: Option<String>,
353}
354
355/// response of select row, you can use serde_json::Value as T or any struct that implements Deserialize
356#[derive(Debug, Clone, Deserialize)]
357pub struct SelectRowsResponse<T> {
358    pub code: i32,
359    pub msg: String,
360    pub rows: Vec<T>,
361
362    #[serde(rename = "isTruncated")]
363    pub is_truncated: bool,
364
365    #[serde(rename = "nextMarker")]
366    pub next_marker: serde_json::Value,
367}
368
369/**
370 * batch search rows args response with [BatchSearchRowsResponse],
371 * basing ann search of vector fields, support filter by scalar fields,
372 * you can input multiple vectors to search, and get result for every vector
373 */
374#[derive(Debug, Clone, Builder, Serialize)]
375pub struct BatchSearchRowsArgs {
376    #[builder(setter(into))]
377    pub database: String,
378    #[builder(setter(into))]
379    pub table: String,
380    #[builder(setter(into))]
381    pub anns: BatchAnnsSearchParams,
382
383    #[builder(default, setter(strip_option))]
384    #[serde(rename = "partitionKey", skip_serializing_if = "Option::is_none")]
385    pub partition_ey: Option<serde_json::Value>,
386
387    #[builder(default, setter(into, strip_option))]
388    #[serde(rename = "projections", skip_serializing_if = "Option::is_none")]
389    pub projections: Option<Vec<String>>,
390
391    #[builder(default, setter(strip_option))]
392    #[serde(rename = "retrieveVector", skip_serializing_if = "Option::is_none")]
393    pub retrieve_vector: Option<bool>,
394
395    #[builder(default, setter(into, strip_option))]
396    #[serde(rename = "readConsistency", skip_serializing_if = "Option::is_none")]
397    pub read_consistency: Option<String>,
398}
399
400/*
401 * batch search rows params is similiar to search rows params,
402 * the difference is that queries can be launched on multiple vectors
403 */
404#[derive(Debug, Clone, Builder, Serialize)]
405pub struct BatchAnnsSearchParams {
406    #[builder(setter(into))]
407    #[serde(rename = "vectorField")]
408    pub vector_field: String,
409
410    /// target vector list
411    #[builder(setter(into))]
412    #[serde(rename = "vectorFloats")]
413    pub vector_floats: Vec<Vec<f64>>,
414
415    #[builder(setter(into))]
416    pub params: VectorSearchParams,
417
418    #[builder(default, setter(into, strip_option))]
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub filter: Option<String>,
421}
422
423#[derive(Debug, Clone, Deserialize)]
424pub struct BatchSearchRowsResponse<T> {
425    pub code: i32,
426    pub msg: String,
427    pub results: Vec<BatchRowResult<T>>,
428}
429
430#[derive(Debug, Clone, Deserialize)]
431pub struct BatchRowResult<T> {
432    /// related target vector
433    #[serde(default, rename = "searchVectorFloats")]
434    pub search_vector_floats: Vec<f64>,
435    pub rows: Vec<RowResult<T>>,
436}
437
438impl<T: Serialize> IntoRequest for InsertRowArgs<T> {
439    fn into_request(
440        self,
441        config: &ClientConfiguration,
442        client: &reqwest_middleware::ClientWithMiddleware,
443    ) -> reqwest_middleware::RequestBuilder {
444        let url = format!("{}/{}/row?insert", config.endpoint, config.version);
445        client.post(url).json(&self)
446    }
447}
448
449impl<T: Serialize> IntoRequest for UpsertRowArgs<T> {
450    fn into_request(
451        self,
452        config: &ClientConfiguration,
453        client: &reqwest_middleware::ClientWithMiddleware,
454    ) -> reqwest_middleware::RequestBuilder {
455        let url = format!("{}/{}/row?upsert", config.endpoint, config.version);
456        client.post(url).json(&self)
457    }
458}
459
460impl IntoRequest for UpdateRowArgs {
461    fn into_request(
462        self,
463        config: &ClientConfiguration,
464        client: &reqwest_middleware::ClientWithMiddleware,
465    ) -> reqwest_middleware::RequestBuilder {
466        let url = format!("{}/{}/row?update", config.endpoint, config.version);
467        client.post(url).json(&self)
468    }
469}
470
471impl IntoRequest for DeleteRowArgs {
472    fn into_request(
473        self,
474        config: &ClientConfiguration,
475        client: &reqwest_middleware::ClientWithMiddleware,
476    ) -> reqwest_middleware::RequestBuilder {
477        let url = format!("{}/{}/row?delete", config.endpoint, config.version);
478        client.post(url).json(&self)
479    }
480}
481
482impl IntoRequest for QueryRowArgs {
483    fn into_request(
484        self,
485        config: &ClientConfiguration,
486        client: &reqwest_middleware::ClientWithMiddleware,
487    ) -> reqwest_middleware::RequestBuilder {
488        let url = format!("{}/{}/row?query", config.endpoint, config.version);
489        client.post(url).json(&self)
490    }
491}
492
493impl IntoRequest for SearchRowsArgs {
494    fn into_request(
495        self,
496        config: &ClientConfiguration,
497        client: &reqwest_middleware::ClientWithMiddleware,
498    ) -> reqwest_middleware::RequestBuilder {
499        let url = format!("{}/{}/row?search", config.endpoint, config.version);
500        client.post(url).json(&self)
501    }
502}
503
504impl IntoRequest for SelectRowsArgs {
505    fn into_request(
506        self,
507        config: &ClientConfiguration,
508        client: &reqwest_middleware::ClientWithMiddleware,
509    ) -> reqwest_middleware::RequestBuilder {
510        let url = format!("{}/{}/row?select", config.endpoint, config.version);
511        client.post(url).json(&self)
512    }
513}
514
515impl IntoRequest for BatchSearchRowsArgs {
516    fn into_request(
517        self,
518        config: &ClientConfiguration,
519        client: &reqwest_middleware::ClientWithMiddleware,
520    ) -> reqwest_middleware::RequestBuilder {
521        let url = format!("{}/{}/row?batchSearch", config.endpoint, config.version);
522        client.post(url).json(&self)
523    }
524}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529    use crate::mochow::{TESTDATABSE, TESTTABLE, UTCLIENT};
530    use anyhow::Result;
531
532    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
533    struct MyRecord {
534        #[serde(default)]
535        id: String,
536        #[serde(default, rename = "bookName")]
537        book_name: String,
538        #[serde(default)]
539        author: String,
540        #[serde(default)]
541        page: i32,
542        #[serde(default)]
543        vector: Vec<f64>,
544    }
545
546    #[test]
547    fn test_insert_row_serialize() -> Result<()> {
548        let args = InsertRowArgsBuilder::default()
549            .database("test_db")
550            .table("test_table")
551            .rows(vec![serde_json::json!(
552                {
553                    "id": "00001",
554                    "username": "alice",
555                    "vector_field": [
556                        0.2323234,
557                        0.34534545,
558                        0.9837234
559                    ]
560                }
561            )])
562            .build()?;
563        let json = serde_json::to_string(&args)?;
564        println!("{}", json);
565        Ok(())
566    }
567
568    #[tokio::test]
569    async fn test_insert_row() -> Result<()> {
570        // insert row with a defined struct
571        let args1 = InsertRowArgsBuilder::default()
572            .database(&TESTDATABSE.to_string())
573            .table(&TESTTABLE.to_string())
574            .rows(vec![MyRecord {
575                id: "0001".to_string(),
576                book_name: "西游记".to_string(),
577                author: "吴承恩".to_string(),
578                page: 21,
579                vector: vec![0.2123, 0.24, 0.213],
580            }])
581            .build()?;
582        let _ret = UTCLIENT.insert_row(&args1).await?;
583        println!("insert_row ret: {:?}", _ret);
584
585        // insert row with a json object
586        let args2 = InsertRowArgsBuilder::default()
587            .database(&TESTDATABSE.to_string())
588            .table(&TESTTABLE.to_string())
589            .rows(vec![serde_json::json!({
590                "id": "0002",
591                "bookName": "西游记",
592                "author": "吴承恩",
593                "page": 22,
594                "vector": [0.2123, 0.24, 0.213],
595            })])
596            .build()?;
597        let _ret = UTCLIENT.insert_row(&args2).await?;
598        println!("insert_row ret: {:?}", _ret);
599        Ok(())
600    }
601
602    #[tokio::test]
603    async fn test_upsert_row() -> Result<()> {
604        let args = UpsertRowArgsBuilder::default()
605            .database(&TESTDATABSE.to_string())
606            .table(&TESTTABLE.to_string())
607            .rows(vec![
608                serde_json::json!({
609                    "id":       "0001",
610                    "bookName": "西游记",
611                    "author":   "吴承恩",
612                    "page":     21,
613                    "vector":   [0.2123, 0.21, 0.213],
614                }),
615                serde_json::json!({
616                    "id":       "0002",
617                    "bookName": "西游记",
618                    "author":   "吴承恩",
619                    "page":     22,
620                    "vector":   [0.2123, 0.22, 0.213],
621                }),
622                serde_json::json!({
623                    "id":       "0003",
624                    "bookName": "三国演义",
625                    "author":   "罗贯中",
626                    "page":     23,
627                    "vector":   [0.2123, 0.23, 0.213],
628                }),
629                serde_json::json!({
630                    "id":       "0004",
631                    "bookName": "三国演义",
632                    "author":   "罗贯中",
633                    "page":     24,
634                    "vector":   [0.2123, 0.24, 0.213],
635                }),
636            ])
637            .build()?;
638        let _ret = UTCLIENT.upsert_row(&args).await?;
639        println!("upsert_row ret: {:?}", _ret);
640        Ok(())
641    }
642
643    #[tokio::test]
644    async fn test_update_row() -> Result<()> {
645        let args = UpdateRowArgsBuilder::default()
646            .database(&TESTDATABSE.to_string())
647            .table(&TESTTABLE.to_string())
648            .primary_key(serde_json::json!({
649                "id": "0001",
650            }))
651            .update(serde_json::json!({
652                "bookName": "红楼梦",
653                "author":   "曹雪芹",
654                "page":     100,
655            }))
656            .build()?;
657        let _ret = UTCLIENT.update_row(&args).await?;
658        println!("update_row ret: {:?}", _ret);
659        Ok(())
660    }
661
662    #[tokio::test]
663    async fn test_delete_row() -> Result<()> {
664        let args = DeleteRowArgsBuilder::default()
665            .database(&TESTDATABSE.to_string())
666            .table(&TESTTABLE.to_string())
667            // .primary_key(serde_json::json!({
668            //     "id": "0001",
669            // }))
670            .filter("page >= 22")
671            .build()?;
672        let _ret = UTCLIENT.delete_rows(&args).await?;
673        println!("delete_row ret: {:?}", _ret);
674        Ok(())
675    }
676
677    #[tokio::test]
678    async fn test_query_row() -> Result<()> {
679        let args = QueryRowArgsBuilder::default()
680            .database(&TESTDATABSE.to_string())
681            .table(&TESTTABLE.to_string())
682            .primary_key(serde_json::json!({
683                "id": "0001",
684            }))
685            .projections(vec!["id".to_string(), "bookName".to_string()])
686            .retrieve_vector(false)
687            .build()?;
688        let query_ret: QueryRowsResponse<MyRecord> = UTCLIENT.query_row(&args).await?;
689        println!("query_row ret: {:?}", query_ret.row);
690        let row1 = query_ret.row;
691        let query_ret: QueryRowsResponse<serde_json::Value> = UTCLIENT.query_row(&args).await?;
692        println!("query_row ret: {:?}", query_ret.row);
693        // convert json value to struct
694        let row2 = serde_json::from_value(query_ret.row)?;
695        assert_eq!(row1, row2);
696        Ok(())
697    }
698
699    #[tokio::test]
700    async fn test_select_row() -> Result<()> {
701        let mut args = SelectRowsArgsBuilder::default()
702            .database(&TESTDATABSE.to_string())
703            .table(&TESTTABLE.to_string())
704            .projections(vec![
705                "id".to_string(),
706                "bookName".to_string(),
707                "page".to_string(),
708            ])
709            .filter("page > 21")
710            .limit(1 as u32)
711            .build()?;
712        loop {
713            let ret: SelectRowsResponse<serde_json::Value> = UTCLIENT.select_rows(&args).await?;
714            println!("select_rows ret: {:?}", ret);
715            if !ret.is_truncated {
716                break;
717            } else {
718                args.marker = Some(ret.next_marker);
719            }
720        }
721
722        Ok(())
723    }
724
725    #[tokio::test]
726    async fn test_search() -> Result<()> {
727        let search_args = SearchRowsArgsBuilder::default()
728            .database(&TESTDATABSE.to_string())
729            .table(&TESTTABLE.to_string())
730            .anns(
731                AnnsSearchParamsBuilder::default()
732                    .vector_field("vector")
733                    .vector_floats(vec![0.3123, 0.43, 0.213])
734                    .params(VectorSearchParams::HNSW(HNSWSearchParams {
735                        ef: 200,
736                        limit: 10,
737                        distance_far: None,
738                        distance_near: None,
739                        pruning: false,
740                    }))
741                    .filter("bookName = '三国演义'")
742                    .build()?,
743            )
744            .retrieve_vector(true)
745            .build()?;
746        let ret: SearchRowsResponse<serde_json::Value> = UTCLIENT.search_rows(&search_args).await?;
747        println!("search_rows ret: {:?}", ret.rows);
748        Ok(())
749    }
750
751    #[tokio::test]
752    async fn test_batch_search() -> Result<()> {
753        let batch_ann_params = BatchAnnsSearchParamsBuilder::default()
754            .vector_field("vector")
755            .vector_floats(vec![vec![0.3123, 0.43, 0.213], vec![0.5512, 0.33, 0.43]])
756            .params(VectorSearchParams::HNSW(HNSWSearchParams {
757                ef: 200,
758                limit: 10,
759                distance_far: None,
760                distance_near: None,
761                pruning: false,
762            }))
763            .filter("bookName = '三国演义'")
764            .build()?;
765        let batch_search_args = BatchSearchRowsArgsBuilder::default()
766            .database(&TESTDATABSE.to_string())
767            .table(&TESTTABLE.to_string())
768            .anns(batch_ann_params)
769            .retrieve_vector(true)
770            .build()?;
771        let batch_rets: BatchSearchRowsResponse<serde_json::Value> =
772            UTCLIENT.batch_search_rows(&batch_search_args).await?;
773        for (i, bs) in batch_rets.results.iter().enumerate() {
774            println!("batch: {}, {:?}", i, bs.search_vector_floats);
775            for (j, ss) in bs.rows.iter().enumerate() {
776                println!("{}, {:?}", j, ss);
777            }
778        }
779        Ok(())
780    }
781}