LoginSignup
4
2

More than 3 years have passed since last update.

MongoDB rustの公式ドライバー(alpha版)を試す(2)(構造体を使う)

Last updated at Posted at 2020-01-21

概要

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

環境

実装

Cargo.tomlのdependenciesへの追加

Cargo.toml
[dependencies]

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

コード

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を代入するとどのように挿入されるかみてみます。

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(())
}

item2Noneを代入して挿入しました。
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版)を試す

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2