使用Rust编写MongoDB客户端,并使用正则表达式进行查询
大致内容说明
在我使用Rust实现MongoDB客户端时,我遇到了很多困难,尤其是在使用正则表达式进行集合搜索时。因此,我想在本文中分享我的经验。由于MongoDB和Rust都是我刚接触的,所以可能还有更好的方法。
必需的环境
服务器和客户端都只确认在Linux上运行。
-
- サーバ側
MongoDBがインストールされていること
動作確認バージョン : 4.4.4
インストールの参考サイト
https://docs.mongodb.com/manual/installation/
クライアント側
Rustがインストールされていること
動作確認バージョン
rustc : 1.43.1
cargo : 1.43.0
インストールの参考サイト
https://www.rust-lang.org/tools/install
在服务器上创建MongoDB的集合
首先,在MongoDB中创建搜索目标的样本集合。
- MongoDBにログイン。ユーザ名はパスワードは各環境で任意
$ mongo -u <username> -p <password>
- thomasというdb名で、登場キャラクターのコレクション(character)を作成
> use thomas
> db.createCollection("character")
- ドキュメントの登録
> db.character.insertMany([
{ name: "トーマス", description: "いつでも元気" },
{ name: "パーシー", description: "たよりになるね" },
{ name: "ゴードン", description: "とっても強い" },
{ name: "エミリー", description: "しっかり屋さん" },
{ name: "トップハム", description: "とっても厳しい" },
])
我想在上面的文档中搜索包含指定字符串的内容。
如果是纯粹的MongoDB命令的话,可以这样做:
-
- MongoDB コマンドメモとか書き
https://qiita.com/svjunic/items/285e9cf20169d70aa1fa
可以使用类似于`db.character.find({<搜索条件>},…)`的格式进行搜索,但是需要在Rust中描述如何实现它。
用Rust语言实现客户端
Cargo项目的概要
仅限于示例,因此将仅使用Cargo.toml和main.rs进行最小化配置。
.
├── Cargo.toml # プロジェクトの設定ファイル
└── src
└── main.rs # クライアントを実装するソース
[package]
name = "mongo_client"
version = "0.1.0"
edition = "2018"
[dependencies]
mongodb = "1.2.1" # MongoDBのドライバー
tokio = "0.2.25" # 非同期処理のランタイム
暂时先不使用正则表达式来实现查询
以下的源代码是从thomas.character集合中搜索name字段完全匹配为“Thomas”的文档。虽然在Web上已经提供了一些信息(尽管有些困难…),但实现起来还算顺利。
※虽然不是主要话题,但是MongoDB内的模块基本上是以”异步处理”的方式运行的,所以在调用这些模块的函数内部必须加上async和.await这样的标识。
use mongodb::Collection;
/// DBのコレクション情報を取得
async fn get_collection() -> Collection {
use mongodb::Client;
// MongoDBにアクセスするためのURIを作成
// username,password等の各引数は環境毎に要設定
let mongo_uri = format!(
"mongodb://{}:{}@{}:{}",
username,
password,
hostname,
port,
);
let client = Client::with_uri_str(&mongo_uri)
.await.unwrap();
// コレクションを返す
client.database("thomas")
.collection("character")
}
#[tokio::main]
async fn main() {
// DBのコレクション情報を取得
let coll = get_collection().await;
// mongodbクレートのDocumentモジュールの内容を
// 簡潔に表現するためのマクロ
use mongodb::bson::doc;
// dbにクエリを投げる
let mut cursor = coll.find(
doc!{"name": "トーマス"},
None,
).await.unwrap();
// クエリ結果(cursor)からDocumentを抽出するのに必要
use tokio::stream::StreamExt;
// クエリ結果を出力
while let Some(doc) = cursor.next().await {
println!("{}", doc.unwrap());
}
}
执行后,过滤出名为“托马斯”的文件并输出。
$ cargo run
Compiling mongo_client v0.1.0 (/work/rust/qiita/mongo_client)
Finished dev [unoptimized + debuginfo] target(s) in 9.08s
Running `target/debug/mongo_client`
{ _id: ObjectId("607bd3721566974cfd205900"), name: "トーマス", description: "いつでも元気" }
顺便提一下,main函数中使用的find函数是用来查询MongoDB集合的,第一个参数是用来指定查询条件的。但是也可以不使用doc!宏,而是通过定义MongoDB库中的结构体Document来实现。如果想要指定查询条件的值为非字符串类型(如i32或Datetime等),可能就只能这样写了(猜测)。
#[tokio::main]
async fn main() {
(中略)
// doc!マクロを使わずに
// Documentモジュールでクエリのフィルタを表現
use mongodb::bson::document::Document;
use mongodb::bson::Bson;
// 検索用のフィルタを定義
let mut filter = Document::new();
filter.insert("name".to_string(),
Bson::String("トーマス".to_string()));
// dbにクエリを投げる
let mut cursor = coll.find(
filter,
None,
).await.unwrap();
(中略)
}
尽管尝试部分匹配进行搜索,但是…
接下来,尝试搜索描述字段中包含“非常”的文档。经过一番调查,发现可以通过在搜索值的前后加上斜杠(/)来实现前向匹配、后向匹配和部分匹配搜索。于是尝试了一些不同的参数作为元源码中find函数的参数,但结果都不理想…。
- 以下だとビルドが通らない
// dbにクエリを投げる
let mut cursor = coll.find(
doc!{"description": /とっても/ },
None,
).await.unwrap();
- 以下だとスラッシュを文字列と見なしてしまい、「とっても」を部分一致で検索できない
// dbにクエリを投げる
let mut cursor = coll.find(
doc!{"description": "/とっても/" },
None,
).await.unwrap();
解决方案
通过将搜索值的部分替换为{$regex : (正则表达式)},问题将得到解决!
#[tokio::main]
async fn main() {
(中略)
// mongodbクレートのDocumentモジュールの内容を
// 簡潔に表現するためのマクロ
use mongodb::bson::doc;
// dbにクエリを投げる
let mut cursor = coll.find(
doc!{"description": {"$regex": "^とっても"}},
None,
).await.unwrap();
(中略)
当你实际执行时,能够正确地得到你想要的搜索结果。
$ cargo run
Compiling mongo_client v0.1.0 (/work/rust/qiita/mongo_client)
Finished dev [unoptimized + debuginfo] target(s) in 9.06s
Running `target/debug/mongo_client`
{ _id: ObjectId("607bd3721566974cfd205902"), name: "ゴードン", description: "とっても強い" }
{ _id: ObjectId("607bd3721566974cfd205904"), name: "トップハム", description: "とっても厳しい" }
在不使用宏的情况下,使用正则表达式的方法参考。
只需一种选项,母语为中文时,将以下内容进行改述:
作为参考,如果不使用宏,而是在Document模块中直接使用简单的过滤,可以通过在Document的第二个元素上指定一个Regex类型的值来完成。当我开始写这篇文章时,我认为这种方法是最佳解决方案,但后来意识到可以使用“doc!宏”来实现相同的效果…。
#[tokio::main]
async fn main() {
(中略)
// doc!マクロを使わずに
// Documentモジュールでクエリのフィルタを表現
use mongodb::bson::document::Document;
use mongodb::bson::Regex;
// 検索用の正規表現を定義
let regex = Regex {
pattern: "^とっても".to_string(),
options: "i".to_string(),
};
// 検索用のフィルタを定義
let mut filter = Document::new();
filter.insert("description".to_string(), regex);
// dbにクエリを投げる
let mut cursor = coll.find(
filter,
None,
).await.unwrap();
(中略)