🔎

MeiliSearch使ってみる Rust編

2021/10/09に公開

MeiliSearch使ってみる Rails編
前回はRailsからMeiliSearchを使ってみたので、今度はRustから使ってみたいと思います

環境

  • OS: Ubuntu 20.04
  • MeiliSearch: 0.22.0
  • Rust: 1.55
  • meilisearch-sdk: 0.10.2

準備

meilisearch

前回までと同じ環境を使うので割愛します

Indexのデータ用の構造体を用意する

データ用の構造体を用意しておきます。

models.rs
use meilisearch_sdk::document::Document;
use serde::{Deserialize, Serialize};
use serde_aux::prelude::*;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct User {
    #[serde(deserialize_with = "deserialize_number_from_string")]
    pub id: u64,
    pub given_name: String,
    pub family_name: String,
    pub age: Option<u32>,
}

impl Document for User {
    type UIDType = u64;

    fn get_uid(&self) -> &Self::UIDType {
        &self.id
    }
}
  • serdeでシリアライズ・デシリアライズできるようにしておく
  • モデルの構造体には Document を実装する
    • primary-keyになる値と型を定義する
  • 前回Railsで作成したデータと同じデータを使いたかったが、Railsから作成したIndexはidが文字列になっているので、同じデータを使う為にRust上ではu64にデシリアライズできるように serde_aux を使いました
  • これであれば、Dieselとかで使っている構造体をそのままMeiliSearchにも使うことが出来そうな気もしますね

その他のライブラリは

Cargo.toml
[package]
name = "rust_sample"
version = "0.1.0"
edition = "2018"

[dependencies]
anyhow = "1.0.44"
meilisearch-sdk = "0.10.2"
serde = { version = "1.0.130", features = ["derive"] }
serde-aux = "2.3.0"
tokio = { version = "1.12.0", features = ["full"] }

こんな感じになっております。

検索してみる

https://docs.rs/meilisearch-sdk/0.10.2/meilisearch_sdk/search/struct.Query.html

src/bin/sample.rs
use meilisearch_sdk::{client::*, search::*};
use rust_sample::models::User;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = Client::new("http://localhost:7700", "api-key");
    let index = client.get_or_create("User").await?;

    let users = index
        .search()
        .with_query("本田")
        .with_attributes_to_highlight(Selectors::Some(&["*"]))
        .with_matches(true)
        .execute::<User>()
        .await?;
    println!("{:#?}", users);
    Ok(())
}
SearchResults {
    hits: [
        SearchResult {
            result: User {
                id: 1,
                given_name: "圭佑",
                family_name: "本田",
                age: None,
            },
            formatted_result: Some(
                User {
                    id: 1,
                    given_name: "圭佑",
                    family_name: "<em>本田</em>",
                    age: None,
                },
            ),
            matches_info: Some(
                {
                    "family_name": [
                        MatchRange {
                            start: 0,
                            length: 6,
                        },
                    ],
                },
            ),
        },
        SearchResult {
            result: User {
                id: 6,
                given_name: "たろう",
                family_name: "田中",
                age: Some(
                    1,
                ),
            },
            formatted_result: Some(
                User {
                    id: 6,
                    given_name: "たろう",
                    family_name: "<em>田中</em>",
                    age: Some(
                        1,
                    ),
                },
            ),
            matches_info: Some(
                {
                    "full_name": [
                        MatchRange {
                            start: 0,
                            length: 3,
                        },
                    ],
                    "family_name": [
                        MatchRange {
                            start: 0,
                            length: 6,
                        },
                    ],
                },
            ),
        },
        SearchResult {
            result: User {
                id: 8,
                given_name: "はなこ",
                family_name: "田中",
                age: Some(
                    1,
                ),
            },
            formatted_result: Some(
                User {
                    id: 8,
                    given_name: "はなこ",
                    family_name: "<em>田中</em>",
                    age: Some(
                        1,
                    ),
                },
            ),
            matches_info: Some(
                {
                    "family_name": [
                        MatchRange {
                            start: 0,
                            length: 6,
                        },
                    ],
                },
            ),
        },
    ],
    offset: 0,
    limit: 20,
    nb_hits: 3,
    exhaustive_nb_hits: false,
    facets_distribution: None,
    exhaustive_facets_count: None,
    processing_time_ms: 0,
    query: "本田",
}

こんな感じになります。
SearchResult に型があるのでやっぱりわかりやすい

indexにDocumentの登録

https://docs.rs/meilisearch-sdk/0.10.2/meilisearch_sdk/indexes/struct.Index.html#method.add_or_replace

add_documentsadd_or_replace のaliasらしいです。

use meilisearch_sdk::{client::*, search::*};
use rust_sample::models::User;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = Client::new("http://localhost:7700", "api-key");
    let index = client.get_or_create("User").await?;

    index
        .add_documents(
            &[User {
                id: 1,
                given_name: "大五郎".to_owned(),
                family_name: "焼酎".to_owned(),
                age: None,
            }],
            Some("id"),
        )
        .await?;

    index.set_filterable_attributes(["age"]).await?;

    let users = index
        .search()
        .with_query("焼酎")
        .with_attributes_to_highlight(Selectors::Some(&["*"]))
        .with_matches(true)
        .execute::<User>()
        .await?;
    println!("{:#?}", users);
    Ok(())
}
  • sleep入れてないので、ass_documentsした直後だとまだindexに追加されてなくて、searchしても返ってこない場合があります
  • Documentget_uid っていうのがあるのに、primary_keyが指定できるのはなんでだろう?
    • ※要確認
  • add_or_replaceとadd_or_updateの違いがよく分かってない
    • ※要確認

まとめ

TODO

  • Railsから使ってみる
  • Rustから使ってみる
  • k8sでの運用(冗長化など)

Discussion