mochow_sdk_rust/mochow/api/
table.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 reqwest_middleware::ClientWithMiddleware;
17use serde::{Deserialize, Serialize};
18
19use crate::mochow::{client::IntoRequest, config::ClientConfiguration};
20
21use super::{FieldType, IndexSchema, PartitionType, TableState};
22
23/// click <https://cloud.baidu.com/doc/VDB/s/flrsob0zr> for more details
24
25/**
26 * create table args, response with [crate::mochow::api::CommonResponse]
27 */
28#[derive(Debug, Clone, Builder, Serialize)]
29pub struct CreateTableArgs {
30    #[builder(setter(into))]
31    pub database: String,
32    #[builder(setter(into))]
33    pub table: String,
34
35    /// the description of table
36    #[builder(setter(into, strip_option))]
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub description: Option<String>,
39
40    /// the number of replicas(including primary replica) for a single tablet, range of [1, 10],
41    /// for high availability, it is recommended >= 3,
42    /// note: the number of relicas needs to less than or equal to the number of DataNodes
43    #[builder(setter(into))]
44    pub replication: u32,
45
46    /// The number of partitions in the table, with a value range of \[1,1000\].
47    /// Suggest evaluating the number of partitions based on the expected total number of records.
48    /// Suggest controlling the number of records in a single partition between 1 million and 10 million.
49    #[builder(default, setter(into))]
50    pub partition: Partition,
51
52    /// is suppport dynamic field, default is false
53    #[builder(default = "Some(false)", setter(strip_option))]
54    #[serde(rename = "enableDynamicField", skip_serializing_if = "Option::is_none")]
55    pub enable_dynamic_field: Option<bool>,
56
57    /// schema args for table
58    #[builder(default, setter(into))]
59    pub schema: TableSchema,
60}
61
62#[derive(Debug, Clone, Builder, Default, Serialize, Deserialize)]
63pub struct Partition {
64    /// there is only one "HASH",option currenctly
65    #[builder(default, setter(into))]
66    #[serde(rename = "partitionType")]
67    pub partition_type: PartitionType,
68
69    /// number of tablet for table, range [1, 1000]
70    #[builder(default, setter(into))]
71    #[serde(rename = "partitionNum")]
72    pub partition_num: u32,
73}
74
75#[derive(Debug, Clone, Builder, Default, Serialize, Deserialize)]
76pub struct TableSchema {
77    /// the fields of table
78    #[builder(default, setter(into))]
79    #[serde(default)]
80    pub fields: Vec<FieldSchema>,
81    /// the indexes of table
82    #[builder(default, setter(into))]
83    #[serde(default)]
84    pub indexes: Vec<IndexSchema>,
85}
86
87#[derive(Debug, Clone, Builder, Serialize, Deserialize)]
88pub struct FieldSchema {
89    /// must start with a letter,
90    /// only lowercase and uppercase letters, numbers and underscores are allowed
91    #[builder(default, setter(into))]
92    #[serde(default, rename = "fieldName")]
93    pub field_name: String,
94
95    /// BOOL、INT8、UINT8、INT16、UINT16、INT32、UINT32、INT64、UINT64、FLOAT、DOUBLE、DATE、DATETIME、TIMESTAMP、UUID、STRING、BINARY、TEXT、TEXT_GBK、TEXT_GB18030、FLOAT_VECTOR
96    #[builder(setter(into))]
97    #[serde(rename = "fieldType")]
98    pub field_type: FieldType,
99
100    /// only a single field is supported for primary key currently,
101    /// unsupport field with type: BOOL、FLOAT、DOUBLE、FLOAT_VECTOR
102    #[builder(default, setter(into))]
103    #[serde(default, rename = "primaryKey")]
104    pub primary_key: bool,
105
106    /// only a single field is supported as a partition key, which can be a primary key or not,
107    /// a table can only have one partition key, each record will be hashed and mapped to diffrent partition,
108    /// unsupport field with type: BOOL、FLOAT、DOUBLE、FLOAT_VECTOR
109    #[builder(default, setter(into))]
110    #[serde(default, rename = "partitionKey")]
111    pub partition_key: bool,
112
113    /// is autoincreament, only applicable to primary key fields of type UINT64
114    #[builder(default, setter(into))]
115    #[serde(default, rename = "autoIncrement")]
116    pub auto_increment: bool,
117
118    /// primary key field, partition key field, secondary index key field and vector field cannot be nullable
119    #[builder(default, setter(into))]
120    #[serde(default, rename = "notNull")]
121    pub not_null: bool,
122
123    /// vector dimension. This parameter needs to be specified only when the data type is FLOAT_VECTOR.
124    #[builder(default, setter(into))]
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub dimension: Option<u32>,
127}
128
129/**
130 * drop table args, response with [crate::mochow::api::CommonResponse]
131 */
132#[derive(Debug, Clone, Builder, Serialize)]
133pub struct DropTableArgs {
134    #[builder(setter(into))]
135    pub database: String,
136    #[builder(setter(into))]
137    pub table: String,
138}
139
140/**
141 * list table args, response with [ListTableResponse]
142 */
143#[derive(Debug, Clone, Builder, Serialize)]
144pub struct ListTableArgs {
145    #[builder(setter(into))]
146    pub database: String,
147}
148
149#[derive(Debug, Clone, Deserialize)]
150pub struct ListTableResponse {
151    pub code: i32,
152    pub msg: String,
153
154    #[serde(default)]
155    pub tables: Vec<String>,
156}
157
158/**
159 * descript table args, response with [DescriptTableResponse]
160 */
161#[derive(Debug, Clone, Builder, Serialize)]
162pub struct DescriptTableArgs {
163    #[builder(setter(into))]
164    pub database: String,
165    #[builder(setter(into))]
166    pub table: String,
167}
168
169#[derive(Debug, Clone, Deserialize)]
170pub struct DescriptTable {
171    pub database: String,
172    pub table: String,
173
174    #[serde(rename = "createTime")]
175    pub create_time: String,
176    pub description: String,
177    pub replication: u32,
178
179    pub partition: Partition,
180
181    #[serde(rename = "enableDynamicField")]
182    pub enable_dynamic_field: bool,
183
184    pub state: TableState,
185
186    #[serde(default)]
187    pub aliases: Vec<String>,
188
189    #[serde(default)]
190    pub schema: TableSchema,
191}
192
193#[derive(Debug, Clone, Deserialize)]
194pub struct DescriptTableResponse {
195    pub code: i32,
196    pub msg: String,
197
198    pub table: DescriptTable,
199}
200
201/**
202 * add field args, response with [crate::mochow::api::CommonResponse]
203 */
204#[derive(Debug, Clone, Builder, Serialize)]
205pub struct AddFieldArgs {
206    #[builder(setter(into))]
207    pub database: String,
208    #[builder(setter(into))]
209    pub table: String,
210    /// schema args for table
211    #[builder(default, setter(into))]
212    pub schema: TableSchema,
213}
214
215/**
216 * stats table args, response with [StatsTableResponse]
217 */
218#[derive(Debug, Clone, Builder, Serialize)]
219pub struct StatsTableArgs {
220    #[builder(setter(into))]
221    pub database: String,
222    #[builder(setter(into))]
223    pub table: String,
224}
225
226#[derive(Debug, Clone, Deserialize)]
227pub struct StatsTableResponse {
228    pub code: i32,
229    pub msg: String,
230
231    #[serde(rename = "rowCount")]
232    pub row_count: u64,
233    #[serde(rename = "memorySizeInByte")]
234    pub memory_size_in_byte: u64,
235    #[serde(rename = "diskSizeInByte")]
236    pub disk_size_in_byte: u64,
237}
238
239/**
240 * alias table args, response with [crate::mochow::api::CommonResponse]
241 */
242#[derive(Debug, Clone, Builder, Serialize)]
243pub struct AliasTableArgs {
244    #[builder(setter(into))]
245    pub database: String,
246    #[builder(setter(into))]
247    pub table: String,
248    #[builder(setter(into))]
249    pub alias: String,
250}
251
252/**
253 * unalias table args, response with [crate::mochow::api::CommonResponse]
254 */
255#[derive(Debug, Clone, Builder, Serialize)]
256pub struct UnaliasTableArgs {
257    #[builder(setter(into))]
258    pub database: String,
259    #[builder(setter(into))]
260    pub table: String,
261    #[builder(setter(into))]
262    pub alias: String,
263}
264
265impl IntoRequest for CreateTableArgs {
266    fn into_request(
267        self,
268        config: &ClientConfiguration,
269        client: &ClientWithMiddleware,
270    ) -> reqwest_middleware::RequestBuilder {
271        let url = format!("{}/{}/table?create", config.endpoint, config.version);
272        client.post(url).json(&self)
273    }
274}
275
276impl IntoRequest for DropTableArgs {
277    fn into_request(
278        self,
279        config: &ClientConfiguration,
280        client: &ClientWithMiddleware,
281    ) -> reqwest_middleware::RequestBuilder {
282        let url = format!("{}/{}/table", config.endpoint, config.version);
283        client.delete(url).json(&self)
284    }
285}
286
287impl IntoRequest for ListTableArgs {
288    fn into_request(
289        self,
290        config: &ClientConfiguration,
291        client: &ClientWithMiddleware,
292    ) -> reqwest_middleware::RequestBuilder {
293        let url = format!("{}/{}/table?list", config.endpoint, config.version);
294        client.post(url).json(&self)
295    }
296}
297
298impl IntoRequest for DescriptTableArgs {
299    fn into_request(
300        self,
301        config: &ClientConfiguration,
302        client: &ClientWithMiddleware,
303    ) -> reqwest_middleware::RequestBuilder {
304        let url = format!("{}/{}/table?desc", config.endpoint, config.version);
305        client.post(url).json(&self)
306    }
307}
308
309impl IntoRequest for AddFieldArgs {
310    fn into_request(
311        self,
312        config: &ClientConfiguration,
313        client: &ClientWithMiddleware,
314    ) -> reqwest_middleware::RequestBuilder {
315        let url = format!("{}/{}/table?addField", config.endpoint, config.version);
316        client.post(url).json(&self)
317    }
318}
319
320impl IntoRequest for StatsTableArgs {
321    fn into_request(
322        self,
323        config: &ClientConfiguration,
324        client: &ClientWithMiddleware,
325    ) -> reqwest_middleware::RequestBuilder {
326        let url = format!("{}/{}/table?stats", config.endpoint, config.version);
327        client.post(url).json(&self)
328    }
329}
330
331impl IntoRequest for AliasTableArgs {
332    fn into_request(
333        self,
334        config: &ClientConfiguration,
335        client: &ClientWithMiddleware,
336    ) -> reqwest_middleware::RequestBuilder {
337        let url = format!("{}/{}/table?alias", config.endpoint, config.version);
338        client.post(url).json(&self)
339    }
340}
341
342impl IntoRequest for UnaliasTableArgs {
343    fn into_request(
344        self,
345        config: &ClientConfiguration,
346        client: &ClientWithMiddleware,
347    ) -> reqwest_middleware::RequestBuilder {
348        let url = format!("{}/{}/table?unalias", config.endpoint, config.version);
349        client.post(url).json(&self)
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use anyhow::Result;
356
357    use super::super::{
358        AutoBuildPolicyBuilder, HNSWIndexParam, IndexSchemaBuilder, VectorIndexParams,
359    };
360    use super::*;
361    use crate::mochow::api::{AutoBuildPolicyType, IndexType, MetricType};
362    use crate::mochow::{TESTDATABSE, TESTTABLE, UTCLIENT};
363
364    #[test]
365    fn create_table_args_serialize_test() -> Result<()> {
366        let args = CreateTableArgsBuilder::default()
367            .database("test_db")
368            .table("test_table")
369            .description("this is description".to_string())
370            .replication(3 as u32)
371            .enable_dynamic_field(true)
372            .partition(
373                PartitionBuilder::default()
374                    .partition_num(1 as u32)
375                    .partition_type(PartitionType::HASH)
376                    .build()?,
377            )
378            .schema(
379                TableSchemaBuilder::default()
380                    .fields(vec![FieldSchemaBuilder::default()
381                        .field_name("name")
382                        .field_type(FieldType::TEXT)
383                        .dimension(0)
384                        .build()?])
385                    .indexes(vec![IndexSchemaBuilder::default()
386                        .index_name("name")
387                        .index_type(IndexType::HNSW)
388                        .params(VectorIndexParams::HNSW(HNSWIndexParam {
389                            m: 8,
390                            ef_construction: 200,
391                        }))
392                        .metric_type(MetricType::L2)
393                        .auto_build(true)
394                        .auto_build_policy(
395                            AutoBuildPolicyBuilder::default()
396                                .policy_type(AutoBuildPolicyType::PERIODICAL)
397                                .build()?,
398                        )
399                        .build()?])
400                    .build()?,
401            )
402            .build()?;
403        let json = serde_json::to_value(args)?;
404        assert_eq!(
405            json,
406            serde_json::json!({
407                "database": "test_db",
408                "table": "test_table",
409                "enableDynamicField": true,
410                "replication": 3,
411                "description": "this is description",
412                "partition": {
413                    "partitionType": "HASH",
414                    "partitionNum": 1,
415                },
416                "schema": {
417                    "fields": [
418                        {
419                            "fieldName": "name",
420                            "fieldType": "TEXT",
421                            "partitionKey": false,
422                            "primaryKey": false,
423                            "autoIncrement": false,
424                            "notNull": false,
425                            "dimension": 0,
426                        }
427                    ],
428                    "indexes": [
429                        {
430                            "autoBuild": true,
431                            "autoBuildPolicy":  {
432                                "periodInSecond":0,
433                                "policyType": "PERIODICAL",
434                                "rowCountIncrement":0,
435                                "rowCountIncrementRatio": 0.0
436                            },
437                            "field": "",
438                            "indexName": "name",
439                            "indexType": "HNSW",
440                            "metricType": "L2",
441                            "params":  {
442                                "M":8,
443                                "efConstruction": 200
444                            }
445                        }
446                    ]
447                }
448            })
449        );
450        println!("{:?}", json);
451        Ok(())
452    }
453
454    #[test]
455    fn descript_table_response_deserialize_test() -> Result<()> {
456        let data = r#"
457            {
458                "database": "test_db",
459                "table": "test_table",
460                "createTime": "2024-02-02T12:02:08Z",
461                "enableDynamicField": true,
462                "state": "NORMAL",
463                "replication": 3,
464                "description": "this is description",
465                "partition": {
466                    "partitionType": "HASH",
467                    "partitionNum": 1
468                },
469                "schema": {
470                    "fields": [
471                        {
472                            "fieldName": "name",
473                            "fieldType": "TEXT",
474                            "partitionKey": false,
475                            "primaryKey": false,
476                            "autoIncrement": false,
477                            "notNull": false,
478                            "dimension": 0
479                        }
480                    ],
481                    "indexes": [
482                        {
483                            "autoBuild": true,
484                            "autoBuildPolicy":  {
485                                "periodInSecond":0,
486                                "policyType": "TIMING",
487                                "rowCountIncrement":0,
488                                "rowCountIncrementRatio": 0.0,
489                                "timing": ""
490                            },
491                            "field": "",
492                            "indexName": "name",
493                            "indexType": "HNSW",
494                            "metricType": "L2",
495                            "state": "NORMAL",
496                            "params":  {
497                                "M":8,
498                                "efConstruction": 20
499                            }
500                        }
501                    ]
502                }
503            }
504        "#;
505
506        let v: DescriptTable = serde_json::from_str(&data)?;
507        println!("{:?}", v);
508        Ok(())
509    }
510
511    #[tokio::test]
512    async fn test_create_table() -> Result<()> {
513        let fields = vec![
514            FieldSchemaBuilder::default()
515                .field_name("id")
516                .field_type(FieldType::STRING)
517                .primary_key(true)
518                .partition_key(true)
519                .not_null(true)
520                .build()?,
521            FieldSchemaBuilder::default()
522                .field_name("bookName")
523                .field_type(FieldType::STRING)
524                .not_null(true)
525                .build()?,
526            FieldSchemaBuilder::default()
527                .field_name("author")
528                .field_type(FieldType::STRING)
529                .build()?,
530            FieldSchemaBuilder::default()
531                .field_name("page")
532                .field_type(FieldType::UINT32)
533                .build()?,
534            FieldSchemaBuilder::default()
535                .field_name("vector")
536                .field_type(FieldType::FLOAT_VECTOR)
537                .not_null(true)
538                .dimension(3)
539                .build()?,
540        ];
541        let indexes = vec![
542            IndexSchemaBuilder::default()
543                .index_name("book_name_idx")
544                .field("bookName")
545                .index_type(IndexType::SECONDARY_INDEX)
546                .build()?,
547            IndexSchemaBuilder::default()
548                .index_name("vector_idx")
549                .field("vector")
550                .index_type(IndexType::HNSW)
551                .metric_type(MetricType::L2)
552                .params(VectorIndexParams::HNSW(HNSWIndexParam {
553                    m: 32,
554                    ef_construction: 200,
555                }))
556                .build()?,
557        ];
558        let args = CreateTableArgsBuilder::default()
559            .database(TESTDATABSE.to_string())
560            .table(TESTTABLE.to_string())
561            .description("basic test".to_string())
562            .replication(3 as u32)
563            .partition(Partition {
564                partition_type: PartitionType::HASH,
565                partition_num: 3,
566            })
567            .schema(TableSchema {
568                fields: fields,
569                indexes: indexes,
570            })
571            .build()?;
572        let create_table_resp = UTCLIENT.create_table(&args).await?;
573        println!("{:?}", create_table_resp);
574        Ok(())
575    }
576
577    #[tokio::test]
578    async fn test_add_field() -> Result<()> {
579        let fields = vec![FieldSchemaBuilder::default()
580            .field_name("bookAlias")
581            .field_type(FieldType::STRING)
582            .build()?];
583        let args = AddFieldArgsBuilder::default()
584            .database(TESTDATABSE.to_string())
585            .table(TESTTABLE.to_string())
586            .schema(TableSchema {
587                fields: fields,
588                indexes: vec![],
589            })
590            .build()?;
591        let ret = UTCLIENT.add_field(&args).await?;
592        println!("{:?}", ret);
593        Ok(())
594    }
595
596    #[tokio::test]
597    async fn test_desc_table() -> Result<()> {
598        let ret = UTCLIENT.desc_table(&TESTDATABSE, &TESTTABLE).await?;
599        println!("{:?}", ret);
600        Ok(())
601    }
602
603    #[tokio::test]
604    async fn test_list_table() -> Result<()> {
605        let ret = UTCLIENT.list_table(&TESTDATABSE).await?;
606        println!("{:?}", ret);
607        Ok(())
608    }
609
610    #[tokio::test]
611    async fn test_stats_table() -> Result<()> {
612        let ret = UTCLIENT.show_table_stats(&TESTDATABSE, &TESTTABLE).await?;
613        println!("{:?}", ret);
614        Ok(())
615    }
616
617    #[tokio::test]
618    async fn test_alias_table() -> Result<()> {
619        let ret = UTCLIENT
620            .alias_table("book", "book_segments", "table_alias1")
621            .await?;
622        println!("{:?}", ret);
623        Ok(())
624    }
625
626    #[tokio::test]
627    async fn test_unalias_table() -> Result<()> {
628        let ret = UTCLIENT
629            .unalias_table("book", "book_segments", "table_alias1")
630            .await?;
631        println!("{:?}", ret);
632        Ok(())
633    }
634
635    #[tokio::test]
636    async fn test_drop_table() -> Result<()> {
637        let ret = UTCLIENT.drop_table(&TESTDATABSE, &TESTTABLE).await?;
638        println!("{:?}", ret);
639        Ok(())
640    }
641}