使用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();

    (中略)

广告
将在 10 秒后关闭
bannerAds