概要
今回は構造体を使ったinsert,find
を実装する。また、構造体の一部のフィールドについてデータが無いときの対処も考える。
環境
- Windows 10
- MongoDB Community版 4.2.2
- rust 1.40.0
- MongoDB rust
- ドライバー 0.9.0 (alpha版)
- bson 0.14.0
- Rustのクレートなど
- serde 1.0.104
実装
Cargo.tomlのdependenciesへの追加
[dependencies]
mongodb ="0.9.0"
bson = "0.14.0"
serde = "1.0.104"
コード
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
を代入するとどのように挿入されるかみてみます。
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
などが残っているが難しくはなさそうです。トランザクション機能はまだ無いのか見つかりませんでした。