web-dev-qa-db-ja.com

部屋での検索の実装

最近、私は Androidアーキテクチャコンポーネント (より具体的には 部屋 )をいじっていますが、少し障害にぶつかりました。

部門とその担当者のリストを格納するRoomデータベースの構築に成功しました。以前は、このデータはサーバーから取得されていましたが、ローカルには保存されていませんでした。検索機能もリモートで処理されていたので、ローカルでも検索機能を処理することを検討していますが、SQLに関する知識が少し不足しています。

サーバー上のSQLコードを見ると、検索ステートメントは一連のREGEXP関数を使用して、提供されたクエリに基づいて両方のデータベースを検索します。これは検索を処理するための最良の方法とは思えませんが、かなりうまく機能し、迅速な応答を示しました。そこで、これをローカルで模倣しようとしましたが、REGEXPがAndroid(NDKを使用しない場合)ではサポートされていないことがすぐにわかりました。

LIKEおよびGLOB演算子に関しては、実行できることは非常に限られているようです。たとえば、一度に複数のキーワードと照合できる方法がわかりません。一方、REGEXPを使用すると、空白をor|)演算子に置き換えるだけで、この機能を実現できます。

それで、私が出くわした代替案を探しています 全文検索 (FTS);これは 検索の実装に関するAndroidドキュメント で示されている方法です。 FTSは完全なドキュメントを検索するためのもののようですが、私のユースケースのように単純なデータではありません。

いずれにせよ、FTS Roomではサポートされていません

したがって、当然のことながら、それを実行する SupportSQLiteOpenHelper.Factory の実装を作成することにより、Roomに標準テーブルではなくFTS仮想テーブルを作成させようとしました。この実装は、デフォルトのFrameworkSQLiteOpenHelperFactoryおよび関連するフレームワーククラスのほぼ直接のコピーです。必要なコードはSupportSQLiteDatabaseにあり、execSQLをオーバーライドして、必要に応じて仮想テーブルコードを挿入します。

class FTSSQLiteDatabase(
    private val delegate: SQLiteDatabase,
    private val ftsOverrides: Array<out String>
) : SupportSQLiteDatabase {

    // Omitted code...

    override fun execSQL(sql: String) {
        delegate.execSQL(injectVirtualTable(sql))
    }

    override fun execSQL(sql: String, bindArgs: Array<out Any>) {
        delegate.execSQL(injectVirtualTable(sql), bindArgs)
    }

    private fun injectVirtualTable(sql: String): String {
        if (!shouldOverride(sql)) return sql

        var newSql = sql

        val tableIndex = sql.indexOf("TABLE")
        if (tableIndex != -1) {
            sql = sql.substring(0..(tableIndex - 1)) + "VIRTUAL " + sql.substring(tableIndex)

            val argumentIndex = sql.indexOf('(')
            if (argumentIndex != -1) {
                sql = sql.substring(0..(argumentIndex - 1) + "USING fts4" + sql.substring(argumentIndex)
            }
        }

        return newSql
    }

    private fun shouldOverride(sql: String): Boolean {
        if (!sql.startsWith("CREATE TABLE")) return false

        val split = sql.split('`')
        if (split.size >= 2) {
            val tableName = split[1]
            return ftsOverrides.contains(tableName)
        } else {
            return false
        }
    }

}

少し面倒ですが、うまくいきます!さて、それは仮想テーブルを作成します…

しかし、次のSQLiteExceptionを取得します。

04-04 10:54:12.146 20289-20386/com.example.app E/SQLiteLog: (1) cannot create triggers on virtual tables
04-04 10:54:12.148 20289-20386/com.example.app E/ROOM: Cannot run invalidation tracker. Is the db closed?
    Android.database.sqlite.SQLiteException: cannot create triggers on virtual tables (code 1): , while compiling: CREATE TEMP TRIGGER IF NOT EXISTS `room_table_modification_trigger_departments_UPDATE` AFTER UPDATE ON `departments` BEGIN INSERT OR REPLACE INTO room_table_modification_log VALUES(null, 0); END
        at Android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
        at Android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.Java:890)
        at Android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.Java:501)
        at Android.database.sqlite.SQLiteSession.prepare(SQLiteSession.Java:588)
        at Android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.Java:58)
        at Android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.Java:31)
        at Android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.Java:1752)
        at Android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.Java:1682)
        at com.example.app.data.FTSSQLiteDatabase.execSQL(FTSSQLiteDatabase.kt:164)
        at Android.Arch.persistence.room.InvalidationTracker.startTrackingTable(InvalidationTracker.Java:204)
        at Android.Arch.persistence.room.InvalidationTracker.access$300(InvalidationTracker.Java:62)
        at Android.Arch.persistence.room.InvalidationTracker$1.run(InvalidationTracker.Java:306)
        at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1162)
        at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:636)
        at Java.lang.Thread.run(Thread.Java:764)

Roomはテーブルを作成しますが、仮想テーブルにトリガーを作成しようとします 明らかに許可されていません 。トリガーをオーバーライドしようとすると(つまり、トリガーが実行されないようにするだけで)、Roomの機能の多くが損なわれると思います。これが、RoomがそもそもFTSをサポートしていない理由だと思います。

TLDR

したがって、RoomがFTSをサポートしておらず(強制的にサポートできない場合)、REGEXPがサポートされていない場合(NDKを使用しない限り)。 Roomの使用中に検索を実装する別の方法はありますか? FTSは正しい方法でさえありますか(やり過ぎのようです)、または私のユースケースにより適した他の方法はありますか?

8
Bryan

私たちはついにそれを手に入れ、バージョンから始めました 2.1.0-alpha01 RoomはマッピングFTS3またはFTS4テーブルを持つエンティティをサポートします。詳細と使用例については、ドキュメントを参照してください: @ Fts および @ Fts4

2
Michał Baran