2021/07/12
Elasticsearchサーバーをローカルで立てて、Rustを使ってアクセスしてみる
以前書いたPythonでの記事のRust版になります。
インストール
Cargo.tomlに依存関係を追加
elasticsearch = "7.12.1-alpha.1"
です。- tokioとserdeは一緒に入れないと不便です。
[dependencies]
elasticsearch = "7.12.1-alpha.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.8.0", features = ["full"] }
dotenv = "0.15.0"
elastic clientの取得
fn get_client() -> Elasticsearch {
let cluster_address = env::var("ELASTICSEARCH_URL")
.unwrap_or("http://localhost:9200".into());
let url = Url::parse(&cluster_address).unwrap();
let conn_pool = SingleNodeConnectionPool::new(url);
let builder = TransportBuilder::new(conn_pool);
let transport = builder.build()
.expect("could not build http transport to elasticsearch...");
Elasticsearch::new(transport)
}
indexの作成
#[tokio::test]
async fn test_create_index_if_not_exists() -> Result<(), Box<dyn Error>> {
dotenv().ok();
let client = get_client();
let index_name = "faq_items";
let exists = client
.indices()
.exists(IndicesExistsParts::Index(&[index_name]))
.send()
.await?;
if exists.status_code() != StatusCode::NOT_FOUND {
println!("index found!");
return Ok(())
}
let response = client
.indices()
.create(IndicesCreateParts::Index(index_name))
.body(json!(
{
"settings": {
"analysis": {
"char_filter": {
"normalize": {
"type": "icu_normalizer",
"name": "nfkc",
"mode": "compose"
}
},
"tokenizer": {
"ja_kuromoji_tokenizer": {
"mode": "search",
"type": "kuromoji_tokenizer",
},
"ja_ngram_tokenizer": {
"type": "ngram",
"min_gram": 2,
"max_gram": 2,
"token_chars": [
"letter",
"digit"
],
},
},
"analyzer": {
"ja_kuromoji_index_analyzer": {
"type": "custom",
"char_filter": [
"normalize",
"html_strip"
],
"tokenizer": "ja_kuromoji_tokenizer",
"filter": [
"kuromoji_baseform",
"kuromoji_part_of_speech",
"cjk_width",
"ja_stop",
"kuromoji_stemmer",
"lowercase"
]
},
"ja_kuromoji_search_analyzer": {
"type": "custom",
"char_filter": [
"normalize",
"html_strip"
],
"tokenizer": "ja_kuromoji_tokenizer",
"filter": [
"kuromoji_baseform",
"kuromoji_part_of_speech",
"cjk_width",
"ja_stop",
"kuromoji_stemmer",
"lowercase"
]
},
"ja_ngram_index_analyzer": {
"type": "custom",
"char_filter": [
"normalize",
"html_strip"
],
"tokenizer": "ja_ngram_tokenizer",
"filter": [
"lowercase"
]
},
"ja_ngram_search_analyzer": {
"type": "custom",
"char_filter": [
"normalize",
"html_strip"
],
"tokenizer": "ja_ngram_tokenizer",
"filter": [
"lowercase"
]
}
}
}
},
"mappings": {
"properties": {
"id": {
"type": "long"
},
"title": {
"type": "text",
"search_analyzer": "ja_kuromoji_search_analyzer",
"analyzer": "ja_kuromoji_index_analyzer"
},
"body": {
"type": "text",
"search_analyzer": "ja_kuromoji_search_analyzer",
"analyzer": "ja_kuromoji_index_analyzer",
"fields": {
"ngram": {
"type": "text",
"search_analyzer": "ja_ngram_search_analyzer",
"analyzer": "ja_ngram_index_analyzer"
}
}
}
}
}
}
))
.send()
.await?;
assert!(response.status_code().is_success(), "response status should be 2**");
Ok(())
}
documentをindex
- indexに含める構造体を定義しておきます
- jsonにserialize/deserializeできるようにしておくと便利です
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct FaqItem {
pub id: i32,
pub title: String,
pub body: String,
}
- FaqItemを先程作ったindexに追加(または更新)します
#[tokio::test]
async fn test_create_or_update_document() -> Result<(), Box<dyn Error>> {
dotenv().ok();
let client = get_client();
let index_name = "faq_items";
let faq_item = FaqItem {
id: 1,
title: "システムメンテナンスのお知らせ".into(),
body: "2021年3月12日23:00よりシステムメンテナンスを実施します。".into(),
};
let response = client
.index(IndexParts::IndexId(index_name, faq_item.id.to_string().as_str()))
.body(faq_item)
.send()
.await?;
assert!(response.status_code().is_success(), "response status should be 2**");
Ok(())
}
documentを検索
#[tokio::test]
async fn test_search_document() -> Result<(), Box<dyn Error>> {
dotenv().ok();
let client = get_client();
let index_name = "faq_items";
let response = client
.search(SearchParts::Index(&[index_name]))
.from(0)
.size(10)
.body(json!({
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "システム",
"fields": [
"body.ngram^1"
],
"type": "phrase"
}
}
],
"should": [
{
"multi_match": {
"query": "システム",
"fields": [
"body^1"
],
"type": "phrase"
}
}
]
}
}
}))
.send()
.await?;
assert!(response.status_code().is_success(), "response status should be 2**");
let response_body = response.json::<Value>().await?;
println!("{:?}", response_body);
let faq_items: Vec<FaqItem> = response_body["hits"]["hits"]
.as_array()
.unwrap()
.iter()
.map(|hit| serde_json::from_value(hit["_source"].clone()).unwrap())
.collect();
for faq_item in faq_items {
println!("{:?}", faq_item)
}
Ok(())
}
感想
elasticsearchのrust client・・とても便利でした。
rustのライブラリは入れたあと、使うまで戸惑うことも多いのですが、比較的簡単に操作できました。
関連する記事
Concordiumノードをローカルで動かしてみた
Concordiumの調査のために、ローカルでソースコードをビルドしてノードを動かしてみました
[Rust]axumとdragonflyを使ったWebsocket Chatのサンプル実装
redis互換のdragonflyをPUBSUBとして利用して、Websocket Chatアプリのサンプル実装を行いました。
[Rust]TiDBを使ったサンプルアプリケーションの実装
RustからTiDBを使ったアプリケーションの実装を行いました。
[Rust]Google Cloud Storageを利用する
GCSやNFSのファイルを扱えるpackageをRustで実装しました。