今日は関数の話です
セットアップ辺りのメソッドは前回掲載されたものを使っています。
variable | type |
---|---|
db |
datomic.Database |
上記は断りもなく出てきます。ご了承ください。
関数
RDBMSの各種ミドルウェアでは組み込みの関数が提供されていますが、datomicも例外ではありません。関数を記述する際の書式は次のとおりになります。
[(function arguments*) output-binding]
これは関数function
をarguments*
に対して適用して、その結果をoutput-binding
に拘束します。
例えば、java.lang.String
型の変数(?a
)のメソッドcompareTo
を別のjava.lang.String
型の変数(?b
)に適用して、結果(java.lang.Integer
)を変数(?r
)に拘束する関数の書式は次のようになります。
[(.compareTo ?a ?b) ?r]
なお、チュートリアルドキュメントによれば、java.lang
で定義されている関数およびclojure.lang
で定義されている関数は特に断りもなく利用可能であるとのことです。
では、実際にサンプルコードを動かしてみます。
@Test void 関数を使う() { def results = Peer.query($/[ :find [?name ...] :where [?c :community/name ?name] [(.compareTo ?name "C") ?fun-result] [(< ?fun-result 0)]]/$ as String, db) def expected = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'].collect { it as char } LOG.debug "[${test.methodName}] results-size: [${results.size()}]" results.each {String name -> assert name.charAt(0) in expected } }
ここで記述されている関数では次のように評価されます。
:community/name
に拘束された文字列?name
のcompareTo
メソッドを文字列"C"
に対して適用して、結果を?fun-result
(java.lang.Integer
型)に拘束する?fun-result
の比較演算<
を数値0
に対して適用する。その結果java.lang.Boolean
が返ってくるので、それがtrue
となるものが抽出される
String#compareTo(String)
は辞書順で比較してメソッドを持っている方のオブジェクトが辞書順で先にある場合はマイナスの値、同じであれば0
、辞書順であとにある場合はプラスの値が返されます。したがって、戻される?name
の値は0 1 2 3 4 5 6 7 8 9 A B
から始まる文字列であることが予想できます。そのため、戻された?name
の最初の1文字目は上記の予想されるchar
のリストの中に含まれることをassert
しています。
実行結果は次の通りで、すべてのassert
を通過します。
results-size: [25]
全文検索
スキーマの定義によって、全文検索インデックスを貼ってある要素に対しては全文検索をかけることが可能です。サンプルのスキーマ定義では、:community/name
および:community/category
に対して全文検索インデックスが作成されているとのことで、これらに対してはfulltext
なる関数を適用することが可能です。
fulltext
関数の戻り値は、検索がヒットしたエンティティと要素とのタプルが返されます。
では、:community/name
に対して全文検索するサンプルコードを試してみます。
@Test void 全文検索() { def results = Peer.query($/[ :find ?name ?db-id :where [(fulltext $ :community/name "Wallingford") [[?db-id ?name]]]]/$, db) assert results.size() == 1 LOG.debug "[${test.methodName}] results-size: [${results.size()}]" def vec = results[0] assert vec[0].contains('Wallingford') LOG.debug "[${test.methodName}] name:[${vec[0]}], db-id?: [class: [${vec[1].getClass()}], value: [${vec[1]}]]" }
実行するとすべてのassert
をパスして、次のように表示されます。
results-size: [1] name:[KOMO Communities - Wallingford], db-id?: [class: [class java.lang.Long], value: [17592186045605]]
fulltext
関数で返されるエンティティはjava.lang.Long
の数値なので、初日の辺りにdb.entity(Long)
でEntityMap
を得たような操作ができるものと思われます。
早速試してみましょう。
@Test void fulltext関数が何を返しているのか() { def results = Peer.query($/[ :find ?obj :in $ ?keyword :where [(fulltext $ :community/name ?keyword) [[?obj]]]]/$, db, 'Wallingford') assert results.size() == 1 def vec = results[0] LOG.debug "[${test.methodName}] result: [class: ${vec[0].getClass()}, value: [${vec[0]}]]" EntityMap entity = db.entity(vec[0]) LOG.debug "[${test.methodName}] entity: [value: [${entity.get(':community/name')}]]" }
実行結果はこのとおりです。
result: [class: class java.lang.Long, value: [17592186045605]] entity: [value: [KOMO Communities - Wallingford]]
見事、予想通りでした。
全文検索の検索キーワードをパラメーターで渡す
それでは、4日目の復習と5日目のまとめ。
全文検索のキーワードはクエリの中に仕込むのではなく、パラメーターで与えたほうが、やりやすいというわけで、全文検索のキーワードをパラメーターで与えるクエリーを試します。
@Test void 全文検索のキーワードをパラメタで渡す() { def results = Peer.query($/[ :find ?name ?category :in $ ?type ?search :where [?c :community/name ?name] [?c :community/type ?type] [(fulltext $ :community/category ?search) [[?c ?category]]]]/$ as String, db, ':community.type/website', 'food') assert results.size() == 2 results.each {vec -> assert vec[1].contains('food') LOG.debug "[${test.methodName}] name: [${vec[0]}], category: [${vec[1]}]" } }
このクエリーのパラメーターは
?type
::community/type
の絞り込み条件?search
:fulltext
の:community/category
に対する検索キーワード
となっています。
取得するものが、:community/name
と:community/category
なので、とりあえず取得された:community/category
に対して検索キーワードを含む文字列が返されていることでassert
しています。
実行結果は次のとおり
name: [Community Harvest of Southwest Seattle], category: [sustainable food] name: [InBallard], category: [food]
このように、food
が:community/category
に含まれているものが返ってきました。
以上。
明日はクエリールール、明後日はdatomicの目玉機能である時間軸とどう相対していくかを扱う予定です。