Edited at

vte.cxによるバックエンドを不要にする開発(4.全文検索とOR検索)

前回=> vte.cxによるバックエンドを不要にする開発(3.スキーマ定義と型の利用)

今回は条件検索や全文検索、OR検索など様々な検索を行う方法について説明します。


条件検索

前回の記事において、http://{サービス名}.vte.cx/d/foo?x&fをGETすることで、foo配下のentryのリストを取得できることを説明しましたが、さらに、URLパラメータに検索条件を加えることでデータの絞り込みを行うことができます。

つまり、user.name=bazという条件で絞り込みたければ、http://{サービス名}.vte.cx/d/foo?x&f&user.name=bazというようにURLパラメータに検索条件を追加すればいいわけです。

条件検索の文法は以下の通りです。

https://{サービス名}.vte.cx/{Key}?f&{name}{=|-eq-|-lt-|-le-|-gt-|-ge-|-ne-|-rg-|-fm-|-bm-|-ft-}{value}&{name}{=|-eq-|-lt-|-le-|-gt-|-ge-|-ne-|-rg-|-fm-|-bm-|-ft-}{value}&...&l={n}&p={カーソル文字列}&s={ソート項目名}

記号の種類と意味は以下の通りです。

eq : = (等しい)

lt : < (未満)
le : <= (以下)
gt : > (より大きい)
ge : >= (以上)
ne : != (等しくない)
rg : regex (正規表現に合致する)
fm : 前方一致
bm : 後方一致
ft : 全文検索

前方一致では、指定した文字の先頭文字が一致する条件となり、後方一致では、末尾文字が一致する条件になります。SQLで例えると、-fm-町は、LIKE '町%'に相当し、-bm-田は、LIKE '%田'に相当します。

あいまい検索を実行するには正規表現を使います。例えば、 -rg-.田町.は、`LIKE '%田町%'に相当します。

詳しくは、ドキュメントを参照してください。

では、実際に条件検索を実行する以下のコードを見てみましょう。axios.get('/d/foo?f&user.name=baz')が条件検索を実行する箇所になります。検索結果は複数件戻る可能性がありますのでentryの配列(VtecxApp.Entry[])になっている点に注意してください。


index.tsx

import * as React from 'react'

import * as ReactDOM from 'react-dom'
import { useState,useEffect } from 'react'
import axios from 'axios'

const App = () => {
const [x, f] = useState(0)

const getdata = async () => {
try {
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
const res = await axios.get('/d/foo?f&user.name=baz')
const feed: VtecxApp.Entry[] = res.data
if (feed.length>0&&feed[0].user&&feed[0].user.name) {
alert(`res= ${feed[0].user.name} `);
}
} catch (e) {
alert('error')
console.log(e)
}
}

useEffect(() => {
getdata()
})

return (
<div>
<button onClick={() => { f(x+1) }}>
{x} times
</button>
</div>
)

}

ReactDOM.render(<App/>, document.getElementById('container'))


これを実行して、res= bazが表示されたら成功です。


indexの設定

条件検索はメモリ内で実行しますが、データ量が多いとパフォーマンス悪化を招きます。そのような場合、Indexを設定することで、パフォーマンスを向上させることができます。

ただし、Index検索を適用できるのは検索条件の最初の項目(一番左の項目)のみとなります。2番目以降の項目についてはIndex検索で絞り込み後にメモリ内で検索を実行します。例えば、?foo=123&bar=456という条件で検索した場合、fooにIndex設定があればIndex検索して絞り込みを行い、それからbarの検索をインメモリで実行します。

Indexの設定はテンプレートで行います。テンプレートとは、(/_settings/template)エントリのことで、Index設定はtemplateエントリのrightsタグに記述します。(管理画面のエントリスキーマ管理でも設定できます)

 以下のように、左辺の項目名に続けて:(コロン)の後に正規表現を記述することでIndexを設定できます。また、右辺に登録するURIのうち正規表現にマッチするものをIndexとして登録します。URIにはエントリの実体(self)や別名(alias)などを指定します。

 以下は、user.name項目を/fooから検索した際にIndex検索となる設定例です。

user.name:/foo  

では、実際にsetup/settings/template.xmlを書き換えてみましょう。

rightsタグの内容がIndex設定になります。


template.xml

<?xml version="1.0" encoding="UTF-8" ?>

<feed>
<entry>
<content>user
name
email</content>
<link href="/_settings/template" rel="self"/>
<rights>user.name:/foo
</rights>
</entry>
<entry>
<link href="/_settings/template_property" rel="self"/>
</entry>
<entry>
<link href="/_settings/template_property/user" rel="self"/>
<title>ユーザ</title>
</entry>
<entry>
<link href="/_settings/template_property/user.email" rel="self"/>
<title>メールアドレス</title>
</entry>
<entry>
<link href="/_settings/template_property/user.name" rel="self"/>
<title>名前</title>
</entry>
</feed>

template.xmlファイルを書き換えたら、以下のコマンドを実行して、サーバを更新してください。

npm run upload:template

以下のように表示されたら成功です。これで、user.nameを/fooから検索する際にIndexが使われるようになります。ただし、index設定を行った後に更新したデータだけが対象になりますので注意してください。(※ index設定する以前に登録したデータは見れなくなります)

$ npm run upload:template

> vtecxblank@1.0.0 upload:template /Users/takezaki/temp/demo/temp/tutorial
> npx vtecxutil upload:template

setup/_settings/template.xml --> http://{サービス名}.vte.cx/_settings/template.xml
{"feed" : {"title" : "Accepted."}}


全文検索の設定

通常の検索以外に全文検索を行うことができます。全文検索を行うには、下記の全文検索Indexを設定したうえで、検索条件の記号を{項目名}-ft-{値}のように指定してください。

全文検索では、検索文字を形態素解析し、形態素解析結果の文字列数分、全文検索indexのクエリ検索を行います。(形態素解析エンジンにはLuceneとlucene-analyzers-kuromoji-ipadic-neologdを使用しています。)

 全文検索Indexの設定はテンプレート(/_settings/template)のrightsタグに記述します。

 以下のように、左辺の項目名に続けて;(セミコロン)の後に正規表現を記述することで全文検索Indexを設定できます。また、右辺に登録するURIのうち正規表現にマッチするものを全文検索Indexとして登録します。URIにはエントリの実体(self)や別名(alias)などを指定します。

 以下は、user.description項目を/fooから検索した際に全文検索となる設定例です。

user.description;/foo  

まず、user.descriptionは新しい項目なのでスキーマに追加します。

管理画面のエントリスキーマ管理画面の新規エントリ項目追加タブでdescription(説明)項目を追加してください。親項目にuserを指定するのを忘れずに。

スクリーンショット 2019-08-28 12.45.48.png

追加できたら以下のコマンドでダウンロードしてローカルのファイル(template.xmlとindex.d.ts)を更新してください。

npm run download:template         // template.xmlの更新

npm run download:typings // index.d.tsの更新

また、ダウンロードしたtemplate.xmlを修正し、user.description;/fooをrightsに追加します。(改行は必要ですが空白などが入らないように注意してください。)


template.xml

<?xml version="1.0" encoding="UTF-8" ?>

<feed>
<entry>
<content>user
name
email
description
</content>
<link href="/_settings/template" rel="self"/>
<rights>user.name:/foo
user.description;/foo
</rights>
</entry>
<entry>
<link href="/_settings/template_property" rel="self"/>
</entry>
<entry>
<link href="/_settings/template_property/user" rel="self"/>
<title>ユーザ</title>
</entry>
<entry>
<link href="/_settings/template_property/user.description" rel="self"/>
<title>説明</title>
</entry>
<entry>
<link href="/_settings/template_property/user.email" rel="self"/>
<title>メールアドレス</title>
</entry>
<entry>
<link href="/_settings/template_property/user.name" rel="self"/>
<title>名前</title>
</entry>
</feed>

修正したら以下のコマンドでサーバを更新してください。

これでuser.description項目を/fooから検索した際に全文検索となります。

npm run upload:template


全文検索の実行

まずは以下のプログラムを実行させて、user.descriptionに文章を登録します。


index.tsx

import * as React from 'react'

import * as ReactDOM from 'react-dom'
import { useState,useEffect } from 'react'
import axios from 'axios'

const App = () => {
const [x, f] = useState(0)

const req: VtecxApp.Entry[] = [
{
user: {
name: 'foo',
email: 'foo@vte.cx',
description: 'フー'
},
link: [
{
"___href": "/foo/1",
"___rel": "self"
}
]
},
{
user: {
name: 'bar',
email: 'bar@vte.cx',
description: 'バー'
},
link: [
{
"___href": "/foo/2",
"___rel": "self"
}
]
},
{
user: {
name: 'baz',
email: 'baz@com',
description: 'バーチャルには仮想という意味の他に「本来の、本質的な」という意味があります。私たちバーチャルテクノロジーは、本質的な価値を追求することをミッションにしています。また、エンドユーザの価値にフォーカスし、Webサービスの迅速な市場投⼊を⽀援していきたいと考えています。それが本質的な価値だと思うからです。'
},
link: [
{
"___href": "/foo/3",
"___rel": "self"
}
]
}
]

const putdata = async () => {
try {
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
const res = await axios.put('/d/foo',req)
alert(`res= ${res.data.feed.title} `);
} catch (e) {
alert('error')
console.log(e)
}
}

useEffect(() => {
putdata()
})

return (
<div>
<button onClick={() => { f(x+1) }}>
{x} times
</button>
</div>
)

}

ReactDOM.render(<App/>, document.getElementById('container'))


次にブラウザーから以下を開いて該当のエントリが検索できることを確認してみましょう。

http://{サービス名}.vte.cx/d/foo?x&f&user.description-ft-バーチャルテクノロジー

以下のように表示できれば成功です。

スクリーンショット 2019-08-28 13.27.22.png


OR検索の実行

&|(と&)でOR条件を囲むことでOR検索ができます。括弧を省略する&|も使用できます。以下はOR条件の例です。(詳しくは、ドキュメントを参照してください)

&|(user.description-ft-バーチャルテクノロジー&)&|(user.name=foo)

&|user.description-ft-バーチャルテクノロジー&|user.name=foo

ブラウザで以下を開いて確認してみてください。

http://{サービス名}.vte.cx/d/foo?f&x&|user.description-ft-バーチャルテクノロジー&|user.name=foo

以下のように2件検索できれば成功です。

このように、条件検索だけでなく全文検索を含めてもOR条件で検索できます。

スクリーンショット 2019-08-28 13.45.16.png


ソートの指定

?s={項目}パラメータを指定すると、その項目は昇順でソートされます。(OR条件で検索した場合でも検索結果すべてについてソートされます。)

ソート指定する項目にIndexの設定がなされているとレスポンスは高速になります。(Index設定がない項目でもソートは可能です)

また、index項目を検索する場合、その項目は昇順ソートされているため、ソート指定は不要です。

ソート指定をした項目が存在しない場合は検索できませんので注意してください。

今回は以上となります。お疲れ様でした。

次回=>vte.cxによるバックエンドを不要にする開発(5.データの一貫性)