この前、Infinispan 9で追加されたIckle QueryをEmbedded Modeで試しました。
Infinispan 9で追加された、Ickle Queryを試す - CLOVER
このIckle Query、どうもRemote Queryでも使えそうな感じなので、試してみようと思います。
それはそれは、たくさんハマりましたが…。
ちなみにこの機能、Infinispanにはドキュメントはないのですが、JBoss Data Gridにはドキュメントがあるので、
こちらが参考になるかもしれません。
Chapter 20. Remote Querying - Red Hat Customer Portal
テーマ
基本的に、Embedded Modeの時と同じような内容をなぞっていきます。検索のテーマは、書籍とします。
使うCacheとQueryとしては、
- 通常のCacheにEntityを登録してリフレクションベースのQueryを試す
- インデキシングを有効にしたCacheに対してインデックス設定を入れたEntityを登録、Full Text Queryを試す
という2つのパターンを試してみたいと思います。
準備
まずは、Infinispan Serverが必要です。
今回は、Infinispan Server 9.0.1.Finalをダウンロードして展開、実行します。
$ wget http://downloads.jboss.org/infinispan/9.0.1.Final/infinispan-server-9.0.1.Final-bin.zip $ unzip infinispan-server-9.0.1.Final-bin.zip
起動は、クラスタリングを前提にして行いましょう。ただ、今回のNode数はひとつとします。
$ cd infinispan-server-9.0.1.Final $ bin/standalone.sh -c clustered.xml
続いて、Cacheの登録を行います。操作は管理CLIで行いますが、最初に管理用のユーザーを作成。「ispn-admin」とします。
$ bin/add-user.sh -u ispn-admin -p password
このユーザーを使って、管理CLIを起動します。
$ bin/ispn-cli.sh -c -u=ispn-admin -p=password [standalone@localhost:9990 /]
最初に、インデックスを利用しないCacheの作成。Distributed Cacheで、「bookCache」とします。
[standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/configurations=CONFIGURATIONS/distributed-cache-configuration=bookCacheConfiguration:add(start=EAGER,mode=SYNC) {"outcome" => "success"} [standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/distributed-cache=bookCache:add(configuration=bookCacheConfiguration) {"outcome" => "success"}
続いて、インデックスを有効にしたCacheの作成。同じくDistributed Cacheで、こちらは「indexedBookCache」とします。
[standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/configurations=CONFIGURATIONS/distributed-cache-configuration=indexedBookCacheConfiguration:add(start=EAGER,mode=SYNC) {"outcome" => "success"} [standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/configurations=CONFIGURATIONS/distributed-cache-configuration=indexedBookCacheConfiguration/indexing=INDEXING:add(indexing=LOCAL,indexing-properties={default.directory_provider=ram,lucene_version=LUCENE_CURRENT}) {"outcome" => "success"} [standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/distributed-cache=indexedBookCache:add(configuration=indexedBookCacheConfiguration) {"outcome" => "success"}
特に変わった設定はしていません。インデックスの保存先はメモリ上とし、Analyzerなどもデフォルトです。
ここまでで、Infinispan Serverの準備はおしまいです。
続いて、アプリケーション側。ライブラリの依存関係としては、このくらいがあればOKです。
libraryDependencies ++= Seq( "org.infinispan" % "infinispan-client-hotrod" % "9.0.1.Final" % Compile, "org.infinispan" % "infinispan-remote-query-client" % "9.0.1.Final" % Compile, "org.infinispan" % "infinispan-query-dsl" % "9.0.1.Final" % Provided, "net.jcip" % "jcip-annotations" % "1.0" % Provided, "org.scalatest" %% "scalatest" % "3.0.3" % Test )
ScalaTestは、テスト用です。今回はScalaで扱っているので、「jcip-annotations」は少なくともJavaで扱う際には
不要です。「infinispan-query-dsl」もしかしたら不要かもしれません。
テストコードの雛形
動作確認はテストコードを使って行いますが、雛形としては以下とします。
src/test/scala/org/littlewings/infinispan/icklequery/IckeQuerySpec.scala
package org.littlewings.infinispan.icklequery import org.infinispan.client.hotrod.configuration.ConfigurationBuilder import org.infinispan.client.hotrod.exceptions.HotRodClientException import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller import org.infinispan.client.hotrod.{RemoteCache, RemoteCacheManager, Search} import org.infinispan.protostream.annotations.ProtoSchemaBuilder import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants import org.scalatest.{FunSuite, Matchers} class IckeQuerySpec extends FunSuite with Matchers { // ここに、テストコードを書く! protected def withCache[K, V](cacheName: String)(fun: RemoteCache[K, V] => Unit): Unit = { val manager = new RemoteCacheManager( new ConfigurationBuilder() .addServer() .host("localhost") .port(11222) .marshaller(new ProtoStreamMarshaller) .build() ) try { val cache = manager.getCache[K, V](cacheName) fun(cache) cache.stop() } finally { manager.stop() } } }
Infinispan Serverに接続する部分については、共通化しました。
ちょっと困ったこと
Remote Queryを使うためには、Protocol BufferesのIDLをInfinispan Serverの内部Cacheに登録する必要があります。このCacheは、アクセス時に
動的に作成されるのですが、これがローカルアドレスからのアクセス以外だと認証必須となり、ちょっと今回は設定をいったん諦めて
Infinispan Serverと確認用のアプリケーションを同じホストで動作させています。
Hot Rodの認証の設定は、また今度確認したいと思います。
追記)
このリモートアクセスの場合の認証必須の設定は、Infinispan 9.1.0.Final/9.0.3.Finalで削除されました。
※しかも、それ以前はどうやっても認証を通過できなかった…
[ISPN-7814] Remove auth check in CacheDecodeContext - JBoss Issue Tracker
https://github.com/infinispan/infinispan/commit/61b3d835e4838d76425dffab532fa507d7954ca8
それほど効果がないと判断されたもよう…。
頑張って設定する場合は、このあたりを参考に…。
8.3. Role Mapping - Red Hat Customer Portal
8.4. Configuring Authentication and Role Mapping using JBoss EAP Login Modules - Red Hat Customer Portal
8.5. Configuring Red Hat JBoss Data Grid for Authorization - Red Hat Customer Portal
8.2. Permissions - Red Hat Customer Portal
JBoss Data Grid 7.0 - Remote Query via Hot Rod Java, Register protobuf marshaller and schema
設定するなら、こういう感じでしょう。
## ユーザー追加 $ bin/add-user.sh -u ispn-admin -p password $ bin/add-user.sh -a -u ispn-user -p password # グループに入れる場合 $ bin/add-user.sh -a -u ispn-user -p password -ro super ## 設定 $ bin/ispn-cli.sh -c -u=ispn-admin -p=password [standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/security=SECURITY:add() [standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/security=SECURITY/authorization=AUTHORIZATION:add(mapper=org.infinispan.security.impl.IdentityRoleMapper) [standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/security=SECURITY/authorization=AUTHORIZATION/role=ROLE:add(name=ispn-user,permissions=[ALL]) [standalone@localhost:9990 /] /subsystem=datagrid-infinispan-endpoint/hotrod-connector=hotrod-connector/authentication=AUTHENTICATION:add(security-realm=ApplicationRealm) [standalone@localhost:9990 /] /subsystem=datagrid-infinispan-endpoint/hotrod-connector=hotrod-connector/authentication=AUTHENTICATION/sasl=SASL:add(server-name=myserver,mechanisms=[DIGEST-MD5])
リフレクションベースのQuery
Embedded Modeと同様、InfinispanのQuery DSLで扱えるQueryには、2種類あります。Cacheのindexingを有効にしている場合はFull Text Queryですが、
そうでない場合はリフレクションベースのQueryになります(全件スキャン)。
まずは、リフレクションベースのQueryから扱っていきたいと思います。
Cacheに登録するEntityは、こちら。
src/main/scala/org/littlewings/infinispan/icklequery/Book.scala
package org.littlewings.infinispan.icklequery import org.infinispan.protostream.annotations.{ProtoField, ProtoMessage} import org.infinispan.protostream.descriptors.Type import scala.annotation.meta.beanGetter import scala.beans.BeanProperty object Book { def apply(isbn: String, title: String, price: Int, category: String): Book = { val book = new Book book.isbn = isbn book.title = title book.price = price book.category = category book } } @ProtoMessage(name = "Book") class Book { @BeanProperty @(ProtoField @beanGetter)(number = 1, name = "isbn", `type` = Type.STRING) var isbn: String = _ @BeanProperty @(ProtoField @beanGetter)(number = 2, name = "title", `type` = Type.STRING) var title: String = _ @BeanProperty @(ProtoField @beanGetter)(number = 3, name = "price", `type` = Type.INT32, defaultValue = "0") var price: Int = _ @BeanProperty @(ProtoField @beanGetter)(number = 4, name = "category", `type` = Type.STRING) var category: String = _ }
Queryの使い方などはEmbedded Modeとそう変わらないのですが、Remote QueryとEmbedded ModeでのQueryで差がある部分を中心に説明していきたいと
思います。
Remote Queryを使うためには、Protocol BuffersのIDLをInfinispan Serverに登録する必要があります。直接IDLを書いてもいいのですが、
今回はIDLをEntityクラスに付与したアノテーションに自動生成させることにします。
以前、同じことはやったのですが、書き方はもうちょっと見直しました。
InfinispanのRemote Queryで、Protocol BuffersのIDLをアノテーションから生成して使う - CLOVER
ポイントとしては、@ProtoMessageアノテーションのnameに指定した値が、Ickle QueryのEntity名になるようです。
@ProtoMessage(name = "Book") class Book {
あと、これはScalaで使っているからの話ですが、@ProtoFieldアノテーションは、@BeanPropertyで生成されるgetterに付与されるように
@beanGetterアノテーションを併用しています。
※@ProtoFieldアノテーションをフィールドに付与する場合、非publicなフィールドには付与できないからです
@BeanProperty @(ProtoField @beanGetter)(number = 1, name = "isbn", `type` = Type.STRING) var isbn: String = _
テストデータは、このように用意。
val books: Array[Book] = Array( Book("978-4798142470", "Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発", 4320, "Spring"), Book("978-4774182179", "[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ", 4104, "Spring"), Book("978-4774161631", "[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン", 3888, "全文検索"), Book("978-4048662024", "高速スケーラブル検索エンジン ElasticSearch Server", 6915, "全文検索"), Book("978-4774183169", "パーフェクト Java EE", 3456, "Java EE"), Book("978-4798140926", "Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築", 4104, "Java EE") )
では、Ickle QueryをRemote Queryで使ってみます。
test("index-less simple Ickle Query") { withCache[String, Book]("bookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("book.proto") .addClass(classOf[Book]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("book.proto", idl) books.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|from Book b |where b.price > 5000 |and b.title = '高速スケーラブル検索エンジン ElasticSearch Server'""".stripMargin) val resultBooks = query.list[Book]() resultBooks should have size (1) resultBooks.get(0).isbn should be("978-4048662024") resultBooks.get(0).title should be("高速スケーラブル検索エンジン ElasticSearch Server") resultBooks.get(0).price should be(6915) resultBooks.get(0).category should be("全文検索") } }
後半はEmbedded Modeで使う場合とほとんど同じなのですが、Remote Queryの場合は先にProtocol BufferesのIDLをInfinispan Serverに登録しておく
必要があります。
IDLは、RemoteCacheManagerとEntityクラスを使用して生成します。
val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("book.proto") .addClass(classOf[Book]) .build(context)
生成したIDLは、Protocol Bufferesのメタデータを登録する専用のCacheに登録します。
val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("book.proto", idl)
このCacheなのですが、先にも書いていますがアクセス時にInfinispan Server側で生成されるものなのですが、ローカルアクセス以外からだと
認証がかかっていないとエラーになります。今回は、ここを端折っています。
あとはデータを登録して、Queryを実行すればOKです。
books.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|from Book b |where b.price > 5000 |and b.title = '高速スケーラブル検索エンジン ElasticSearch Server'""".stripMargin) val resultBooks = query.list[Book]()
この時、Queryに指定するEntityの名前は@ProtoMessageアノテーションで指定したnameの値となります。Embedded Modeの場合は、クラスのFQCNでした。
queryFactory.create(
"""|from Book b
このあたりを把握すれば、Embedded Modeとそう変わらない感じで使うことができると思います。
Queryへのパラメーターの適用や
test("index-less simple Ickle Query, parameterized") { withCache[String, Book]("bookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("book.proto") .addClass(classOf[Book]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("book.proto", idl) books.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|from Book b |where b.price > :price |and b.title = :title""".stripMargin) query.setParameter("price", 5000) query.setParameter("title", "高速スケーラブル検索エンジン ElasticSearch Server") val resultBooks = query.list[Book]() resultBooks should have size (1) resultBooks.get(0).getIsbn should be("978-4048662024") resultBooks.get(0).getTitle should be("高速スケーラブル検索エンジン ElasticSearch Server") resultBooks.get(0).getPrice should be(6915) resultBooks.get(0).getCategory should be("全文検索") } }
Aggregationsも可能です。
test("index-less simple Ickle Query, aggregation") { withCache[String, Book]("bookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("book.proto") .addClass(classOf[Book]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("book.proto", idl) books.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|select b.category, sum(b.price) |from Book b |where b.price > :price |group by b.category |having sum(b.price) > :sumPrice |order by sum(b.price) desc""".stripMargin) query.setParameter("price", 4000) query.setParameter("sumPrice", 5000) val results = query.list[Array[AnyRef]]() results should have size (2) results.get(0)(0) should be("Spring") results.get(0)(1) should be(8424) results.get(1)(0) should be("全文検索") results.get(1)(1) should be(6915) } }
Full Text Query
続いて、Full Text Queryです。
Entityの定義は、こんな感じになりました。
src/main/scala/org/littlewings/infinispan/icklequery/IndexedBook.scala
package org.littlewings.infinispan.icklequery import org.infinispan.protostream.annotations.{ProtoDoc, ProtoField, ProtoMessage} import org.infinispan.protostream.descriptors.Type import scala.annotation.meta.beanGetter import scala.beans.BeanProperty object IndexedBook { def apply(isbn: String, title: String, price: Int, category: String): IndexedBook = { val book = new IndexedBook book.isbn = isbn book.title = title book.price = price book.category = category book } } @ProtoDoc("@Indexed") @ProtoMessage(name = "IndexedBook") class IndexedBook { @BeanProperty @(ProtoDoc @beanGetter)("@Field") @(ProtoField @beanGetter)(number = 1, name = "isbn", `type` = Type.STRING) var isbn: String = _ @BeanProperty @(ProtoDoc @beanGetter)("@Field(analyze = Analyze.YES)") @(ProtoField @beanGetter)(number = 2, name = "title", `type` = Type.STRING) var title: String = _ @BeanProperty @(ProtoDoc @beanGetter)("@Field") @(ProtoField @beanGetter)(number = 3, name = "price", `type` = Type.INT32, defaultValue = "0") var price: Int = _ @BeanProperty @(ProtoDoc @beanGetter)("@Field") @(ProtoField @beanGetter)(number = 4, name = "category", `type` = Type.STRING) var category: String = _ }
先ほどのリフレクションベースで使っていたEntityと異なる点として、@ProtoDocアノテーションを使用して@Indexedを付与しています。Hibernate Searchの
アノテーションでしょうね。
@ProtoDoc("@Indexed") @ProtoMessage(name = "IndexedBook") class IndexedBook {
フィールドに対しては、@ProtoDocアノテーションで@Fieldを付与します。getterに付与されるように、@beanGetterアノテーションを併用しています。
@BeanProperty @(ProtoDoc @beanGetter)("@Field") @(ProtoField @beanGetter)(number = 1, name = "isbn", `type` = Type.STRING) var isbn: String = _
また、デフォルトではAnalyzeされないようなので、Analyzeしたいフィールドに対しては明示的に「analyze = Analyze.YES」を付与する必要があるみたいです。
@BeanProperty @(ProtoDoc @beanGetter)("@Field(analyze = Analyze.YES)") @(ProtoField @beanGetter)(number = 2, name = "title", `type` = Type.STRING) var title: String = _
テストデータは、こちら。
val indexedBooks: Array[IndexedBook] = Array( IndexedBook("978-4798142470", "Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発", 4320, "Spring"), IndexedBook("978-4774182179", "[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ", 4104, "Spring"), IndexedBook("978-4774161631", "[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン", 3888, "全文検索"), IndexedBook("978-4048662024", "高速スケーラブル検索エンジン ElasticSearch Server", 6915, "全文検索"), IndexedBook("978-4774183169", "パーフェクト Java EE", 3456, "Java EE"), IndexedBook("978-4798140926", "Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築", 4104, "Java EE") )
では、使ってみます。最初にリフレクションベースでQueryを書いた時と、そう大きくは変わりません。
test("indexed entity Ickle Query, full text query") { withCache[String, IndexedBook]("indexedBookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("indexed-book.proto") .addClass(classOf[IndexedBook]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("indexed-book.proto", idl) indexedBooks.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|from IndexedBook b |where b.price < 5000 |and b.title: '全文検索'""".stripMargin) val resultBooks = query.list[IndexedBook]() resultBooks should have size (1) resultBooks.get(0).getIsbn should be("978-4774161631") resultBooks.get(0).getTitle should be("[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン") resultBooks.get(0).getPrice should be(3888) resultBooks.get(0).getCategory should be("全文検索") } }
|and b.title: '全文検索'""".stripMargin)
範囲検索も可能です。
test("indexed entity Ickle Query, full text query with range") { withCache[String, IndexedBook]("indexedBookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("indexed-book.proto") .addClass(classOf[IndexedBook]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("indexed-book.proto", idl) indexedBooks.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|from IndexedBook b |where b.price: [6000 TO 7000] |and b.title: '検索'""".stripMargin) val resultBooks = query.list[IndexedBook]() resultBooks should have size (1) resultBooks.get(0).getIsbn should be("978-4048662024") resultBooks.get(0).getTitle should be("高速スケーラブル検索エンジン ElasticSearch Server") resultBooks.get(0).getPrice should be(6915) resultBooks.get(0).getCategory should be("全文検索") } }
Embedded Queryの時と同様、インデックスを有効にしていないCacheおよびEntityに対して、Full Text Queryを使うと例外がスローされます。
test("index-less Ickle Query, can't applied full text predicate") { withCache[String, Book]("bookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("book.proto") .addClass(classOf[Book]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("book.proto", idl) books.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|from Book b |where b.title: '高速スケーラブル検索エンジン ElasticSearch Server'""".stripMargin) val thrown = the[HotRodClientException] thrownBy query.list[Book]() thrown.getMessage should be("org.infinispan.objectfilter.ParsingException: ISPN028521: Full-text queries cannot be applied to property 'title' in type Book unless the property is indexed and analyzed.") } }
反対に、インデックスを有効かつAnalyzedなフィールドに対して「=」などの演算子を使用すると、例外がスローされるのもEmbedded Modeと同じです。
test("indexed entity Ickle Query, analyzed field can't applied eq") { withCache[String, IndexedBook]("indexedBookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("indexed-book.proto") .addClass(classOf[IndexedBook]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("indexed-book.proto", idl) indexedBooks.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|from IndexedBook b |where b.price > 5000 |and b.title = '全文検索'""".stripMargin) val thrown = the[HotRodClientException] thrownBy query.list[IndexedBook]() thrown.getMessage should be("org.infinispan.objectfilter.ParsingException: ISPN028522: No relational queries can be applied to property 'title' in type IndexedBook since the property is analyzed.") } }
Aggregationsについては、ふつうに使えます。
test("indexed entity Ickle Query, full text query, aggregation") { withCache[String, IndexedBook]("indexedBookCache") { cache => val manager = cache.getRemoteCacheManager val context = ProtoStreamMarshaller.getSerializationContext(manager) val protoSchemaBuilder = new ProtoSchemaBuilder val idl = protoSchemaBuilder .fileName("indexed-book.proto") .addClass(classOf[IndexedBook]) .build(context) val metaCache = manager.getCache[String, AnyRef](ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME) metaCache.put("indexed-book.proto", idl) indexedBooks.foreach(b => cache.put(b.isbn, b)) val queryFactory = Search.getQueryFactory(cache) val query = queryFactory.create( """|select b.category, sum(b.price) |from IndexedBook b |where b.title: (+'入門' and -'検索') |group by b.category |order by sum(b.price) desc""".stripMargin) val results = query.list[Array[AnyRef]]() results should have size (2) results.get(0)(0) should be("Spring") results.get(0)(1) should be(8424) results.get(1)(0) should be("Java EE") results.get(1)(1) should be(4104) } }
Embedded Modeといくらか違いはありますが、けっこう似た感じで使うことができました。
まとめ
Infinispan 9から導入されたIckle Queryを、Remote Queryで試してみました。
Remote Queryは、Protol Bufferesを使う関係上ハマりどころもそれなりにあるのですが、Ickle Queryが使えるのは便利かなと思います。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-ickle-query