MongoDB rustの公式ドライバー（alpha版）を試す（２）（構造体を使う）

# 概要

今回は構造体を使った`insert,find`を実装する。また、構造体の一部のフィールドについてデータが無いときの対処も考える。

# 環境

- Windows 10
- MongoDB Community版 4.2.2
- rust 1.40.0
- MongoDB rust
    - ドライバー 0.9.0 (alpha版)
        - https://crates.io/crates/mongodb
        - https://github.com/mongodb/mongo-rust-driver
    - bson 0.14.0
        - https://crates.io/crates/bson
        - https://github.com/mongodb/bson-rust
- Rustのクレートなど    
    - serde 1.0.104



# 実装


## Cargo.tomlのdependenciesへの追加

```toml:Cargo.toml
[dependencies]

mongodb ="0.9.0"
bson = "0.14.0"
serde = "1.0.104"
```

## コード

```rust:main.rs
use mongodb::{error::Result, options::{ClientOptions}, Client};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
pub struct Coll {
    pub item1: f64,
    pub item2: i64,
    pub item3: String 
}

fn main() {
    let client = connect_mongo().expect("failed to connect");
    insert_docs(&client).expect("failed to insert docs");
    find_all_docs(&client).expect("failed to find all docs");
}

fn connect_mongo() -> Result<Client> {
    let mut client_options = ClientOptions::parse("mongodb://localhost:27017")?;
    client_options.app_name = Some("My App".to_string());
    let client = Client::with_options(client_options)?;
    Ok(client)
}

fn insert_docs(client: &Client) -> Result<()> {
    let collection = client.database("test").collection("coll");
    let col_data = Coll{
        item1: 12.3,
        item2: 456,
        item3: String::from("string data")
    };
    let serialized_data = bson::to_bson(&col_data)?; 
    if let bson::Bson::Document(document) = serialized_data {
        collection.insert_one(document, None)?; 
    } else {
        println!("Error converting the BSON object into a MongoDB document");
    }
    Ok(())
}

fn find_all_docs(client: &Client) -> Result<()> {
    let collection = client.database("test").collection("coll");
    let cursor = collection.find(None, None)?;
    for result in cursor {
        match result {
            Ok(document) => {   
                let coll =  bson::from_bson::<Coll>(bson::Bson::Document(document))?;
                println!("item1: {}, item2: {}, item3: {}", coll.item1, coll.item2, coll.item3);
            }
            Err(e) => return Err(e.into()),
        }
    }
    Ok(())
}
```

構造体への代入は`bson::from_bson::<Coll>`を使っています。

```
cargo run
     Running `target\debug\mongorust.exe`
item1: 12.3, item2: 456, item3: string data
```
一部のフィールドが無いデータを作って試します。

```
mongo

> db.coll.insert({item1:11})
WriteResult({ "nInserted" : 1 })
> db.coll.find()
{ "_id" : ObjectId("5e2644d000a7d017003530ee"), "item1" : 12.3, "item2" : NumberLong(456), "item3" : "string data" }
{ "_id" : ObjectId("5e264597469b580a90779fbe"), "item1" : 11 }
```

再びRustのプログラムを実行

```
cargo run
     Running `target\debug\mongorust.exe`
item1: 12.3, item2: 456, item3: string data
thread 'main' panicked at 'failed to find all docs: Error { kind: BsonDecode(ExpectedField("item2")) }', src\libcore\result.rs:1165:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
error: process didn't exit successfully: `target\debug\mongorust.exe` (exit code: 101)
```


構造体のフィールドのデータが無いと上記のようにエラーになります。そこで構造体のデータ型を`Option型`にします。　また、`insert`の時に`None`を代入するとどのように挿入されるかみてみます。


```rust:main.rs
use mongodb::{error::Result, options::{ClientOptions}, Client};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
pub struct Coll {
    pub item1: f64,
    pub item2: Option<i64>,
    pub item3: Option<String>
}

fn main() {
    let client = connect_mongo().expect("failed to connect");
    insert_docs(&client).expect("failed to insert docs");
    find_all_docs(&client).expect("failed to find all docs");
}

fn connect_mongo() -> Result<Client> {
    let mut client_options = ClientOptions::parse("mongodb://localhost:27017")?;
    client_options.app_name = Some("My App".to_string());
    let client = Client::with_options(client_options)?;
    Ok(client)
}

fn insert_docs(client: &Client) -> Result<()> {
    let collection = client.database("test").collection("coll");
    let col_data = Coll{
        item1: 78.9,
        item2: None,
        item3: Some(String::from("string data2"))
    };
    let serialized_data = bson::to_bson(&col_data)?; 
    if let bson::Bson::Document(document) = serialized_data {
        collection.insert_one(document, None)?; 
    } else {
        println!("Error converting the BSON object into a MongoDB document");
    }
    Ok(())
}

fn find_all_docs(client: &Client) -> Result<()> {
    let collection = client.database("test").collection("coll");
    let cursor = collection.find(None, None)?;
    for result in cursor {
        match result {
            Ok(document) => {   
                let coll =  bson::from_bson::<Coll>(bson::Bson::Document(document))?;
                print!("item1: {}", coll.item1);
                match coll.item2 {
                    Some(v) => print!(" item2: {}", v),
                    None => {}
                }
                match coll.item3 {
                    Some(v) => print!(" item3: {}", v), 
                    None => {}
                }
                println!()
            }
            Err(e) => return Err(e.into()),
        }
    }
    Ok(())
}
```


`item2`に`None`を代入して挿入しました。
`find`で構造体の各フィールドが`None`の時はprintしないようにしました。各フィールドに`match`を使う以外の方法が思いつかなかったです。

```
cargo run
     Running `target\debug\mongorust.exe`
item1: 12.3 item2: 456 item3: string data
item1: 11
item1: 78.9 item3: string data2
```
意図通りに無いフィールドは`None`になっていたのでprintされませんでした。
次に`mongo`で確認します。

```
mongo
> db.coll.find()
{ "_id" : ObjectId("5e2644d000a7d017003530ee"), "item1" : 12.3, "item2" : NumberLong(456), "item3" : "string data" }
{ "_id" : ObjectId("5e264597469b580a90779fbe"), "item1" : 11 }
{ "_id" : ObjectId("5e269159008ba689009e7150"), "item1" : 78.9, "item2" : null, "item3" : "string data2" }
```

Rustで`None`で挿入したフィールドの値の`null`になっていました。現時点で構造体に定義してるフィールドを挿入しない方法は分かりませんでした。

# 最後に

Rustの`Error`の扱いと`enum`が良く理解できなくてコードの説明ができない部分があります。
MongoDBのCRUDに関しては`aggregate,update,delete`などが残っているが難しくはなさそうです。トランザクション機能はまだ無いのか見つかりませんでした。

# リンク

[MongoDB rustの公式ドライバー（alpha版）を試す](https://qiita.com/h6591/items/fcdbce05ee8591b443f0)