# HG changeset patch # User Da Risk # Date 1589078285 14400 # Node ID 825fa5987f3cddef8a99fe000dff3a1aa39e70e3 # Parent b25358bd951266f8dedf2077027d6f066f230881 data: Add a composite table to hold Article tags relations diff -r b25358bd9512 -r 825fa5987f3c app/src/androidTest/java/com/geekorum/ttrss/data/migrations/ArticlesDatabaseMigrationTest.kt --- a/app/src/androidTest/java/com/geekorum/ttrss/data/migrations/ArticlesDatabaseMigrationTest.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/androidTest/java/com/geekorum/ttrss/data/migrations/ArticlesDatabaseMigrationTest.kt Sat May 09 22:38:05 2020 -0400 @@ -249,6 +249,15 @@ } } + private fun assertMigration12To13DataIntegrity(db: SupportSQLiteDatabase) { + assertMigration7To8DataIntegrity(db) + db.query("SELECT * FROM ${Tables.ARTICLES_TAGS}").use { + assertThat(it.count).isEqualTo(1) + it.moveToFirst() + assertThat(it.getValue("tag")).isEqualTo("article tags") + } + } + private fun createSomeArticlesFromVersion8(db: SupportSQLiteDatabase) { var values = contentValuesOf( ArticlesContract.Category.TITLE to "category", @@ -416,6 +425,24 @@ } } + @Test + fun migrate12To13() { + helper.createDatabase(TEST_DB, 12).use { + // db has schema version 8. insert some contentData using SQL queries. + // You cannot use DAO classes because they expect the latest schema. + // as our schema for this migration doesn't change much from the previous + // we can reuse the same function + createSomeArticlesFromVersion10(it) + } + + helper.runMigrationsAndValidate(TEST_DB, 13, true, + *ALL_MIGRATIONS.toTypedArray()).use { + // MigrationTestHelper automatically verifies the schema changes, + // but you need to validate that the contentData was migrated properly. + assertMigration12To13DataIntegrity(it) + } + } + private inline fun Cursor.getValue(columnName: String) : T { val index = getColumnIndexOrThrow(columnName) diff -r b25358bd9512 -r 825fa5987f3c app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt --- a/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/androidTest/java/com/geekorum/ttrss/sync/workers/mocks.kt Sat May 09 22:38:05 2020 -0400 @@ -23,6 +23,7 @@ import com.geekorum.ttrss.data.AccountInfo import com.geekorum.ttrss.data.Article import com.geekorum.ttrss.data.ArticleWithAttachments +import com.geekorum.ttrss.data.ArticlesTags import com.geekorum.ttrss.data.Attachment import com.geekorum.ttrss.data.Category import com.geekorum.ttrss.data.Feed @@ -75,6 +76,7 @@ private val articles = mutableListOf
() private val attachments = mutableListOf() private val transactions = mutableListOf() + private val articlesTags = mutableListOf() override suspend fun runInTransaction(block: suspend () -> R) { block() @@ -132,6 +134,10 @@ this.articles.addAll(articles) } + override suspend fun insertArticleTags(articlesTags: List) { + this.articlesTags.addAll(articlesTags) + } + override suspend fun updateArticle(article: Article) { val present = articles.first { it.id == article.id diff -r b25358bd9512 -r 825fa5987f3c app/src/main/java/com/geekorum/ttrss/data/ArticlesDatabase.kt --- a/app/src/main/java/com/geekorum/ttrss/data/ArticlesDatabase.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/main/java/com/geekorum/ttrss/data/ArticlesDatabase.kt Sat May 09 22:38:05 2020 -0400 @@ -24,9 +24,9 @@ import androidx.room.RoomDatabase import com.geekorum.ttrss.providers.PurgeArticlesDao -@Database(entities = [Article::class, ArticleFTS::class, Attachment::class, +@Database(entities = [Article::class, ArticleFTS::class, ArticlesTags::class, Attachment::class, Category::class, Feed::class, Transaction::class, AccountInfo::class], - version = 12) + version = 13) abstract class ArticlesDatabase : RoomDatabase() { abstract fun articleDao(): ArticleDao abstract fun accountInfoDao(): AccountInfoDao @@ -45,5 +45,6 @@ const val TRANSACTIONS = "transactions" const val FEEDS = "feeds" const val CATEGORIES = "categories" + const val ARTICLES_TAGS = "articles_tags" } } diff -r b25358bd9512 -r 825fa5987f3c app/src/main/java/com/geekorum/ttrss/data/SynchronizationDao.kt --- a/app/src/main/java/com/geekorum/ttrss/data/SynchronizationDao.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/main/java/com/geekorum/ttrss/data/SynchronizationDao.kt Sat May 09 22:38:05 2020 -0400 @@ -89,6 +89,9 @@ @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun insertArticles(dataArticles: List
) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract suspend fun insertArticlesTags(articlesTags: List) + @Update(entity = Article::class) abstract suspend fun updateArticlesMetadata(metadata: List) diff -r b25358bd9512 -r 825fa5987f3c app/src/main/java/com/geekorum/ttrss/data/Types.kt --- a/app/src/main/java/com/geekorum/ttrss/data/Types.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/main/java/com/geekorum/ttrss/data/Types.kt Sat May 09 22:38:05 2020 -0400 @@ -26,6 +26,8 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Fts4 +import androidx.room.Index +import androidx.room.Junction import androidx.room.PrimaryKey import androidx.room.Relation import java.text.DateFormat @@ -234,6 +236,28 @@ var unreadCount: Int = 0 ) +@Entity(tableName = "articles_tags", primaryKeys = ["tag", "article_id"], + foreignKeys = [ForeignKey( + entity = Article::class, + parentColumns = ["_id"], + childColumns = ["article_id"], + onDelete = ForeignKey.CASCADE + )]) +data class ArticlesTags( + @ColumnInfo(name = "article_id", index = true) + val articleId: Long, + @ColumnInfo(index = true) + val tag: String +) + +data class TagWithArticles( + val tag: String, +// val articleId: Long, + @Relation(parentColumn = "tag", entityColumn = "_id", + associateBy = Junction(ArticlesTags::class, entityColumn = "article_id")) + val article: List
+) + @Entity(tableName = "feeds", foreignKeys = [ForeignKey(entity = Category::class, diff -r b25358bd9512 -r 825fa5987f3c app/src/main/java/com/geekorum/ttrss/data/migrations/ArticlesDatabase.kt --- a/app/src/main/java/com/geekorum/ttrss/data/migrations/ArticlesDatabase.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/main/java/com/geekorum/ttrss/data/migrations/ArticlesDatabase.kt Sat May 09 22:38:05 2020 -0400 @@ -504,6 +504,47 @@ } +/** + * This migration adds a relation table for articles_tags + */ +object MigrationFrom12To13 : Migration(12, 13) { + override fun migrate(database: SupportSQLiteDatabase) { + addArticleTagsTable(database) + migrateArticleTags(database) + } + + private fun addArticleTagsTable(database: SupportSQLiteDatabase) { + with(database) { + execSQL("""CREATE TABLE IF NOT EXISTS `articles_tags` ( + |`article_id` INTEGER NOT NULL, + |`tag` TEXT NOT NULL, + |PRIMARY KEY(`tag`, `article_id`) + |FOREIGN KEY(`article_id`) REFERENCES `articles`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE + |)""".trimMargin()) + execSQL("CREATE INDEX IF NOT EXISTS `index_articles_tags_article_id` ON `articles_tags` (`article_id`)") + execSQL("CREATE INDEX IF NOT EXISTS `index_articles_tags_tag` ON `articles_tags` (`tag`)") + } + } + + private fun migrateArticleTags(database: SupportSQLiteDatabase) { + with(database) { + query("SELECT _id, tags FROM articles").use { + while (it.moveToNext()) { + val articleId = it.getLong(0) + val tags = it.getString(1) + val tagsList = tags.split(",") + .map(String::trim) + .filter(String::isNotEmpty) + .distinct() + for (tag in tagsList) { + execSQL("INSERT INTO `articles_tags` (`article_id`, `tag`) VALUES (?, ?)", arrayOf(articleId, tag)) + } + } + } + } + } +} + internal val ALL_MIGRATIONS = listOf(MigrationFrom1To2, MigrationFrom2To3, MigrationFrom3To4, @@ -514,4 +555,5 @@ MigrationFrom8To9, MigrationFrom9To10, MigrationFrom10To11, - MigrationFrom11To12) + MigrationFrom11To12, + MigrationFrom12To13) diff -r b25358bd9512 -r 825fa5987f3c app/src/main/java/com/geekorum/ttrss/data/plugins/SynchronizationFacade.kt --- a/app/src/main/java/com/geekorum/ttrss/data/plugins/SynchronizationFacade.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/main/java/com/geekorum/ttrss/data/plugins/SynchronizationFacade.kt Sat May 09 22:38:05 2020 -0400 @@ -25,6 +25,7 @@ import com.geekorum.ttrss.data.AccountInfoDao import com.geekorum.ttrss.data.Article import com.geekorum.ttrss.data.ArticlesDatabase +import com.geekorum.ttrss.data.ArticlesTags import com.geekorum.ttrss.data.Attachment import com.geekorum.ttrss.data.Category import com.geekorum.ttrss.data.Feed @@ -62,6 +63,9 @@ override suspend fun getRandomArticleFromFeed(feedId: Long): Article? = synchronizationDao.getArticleFromFeed(feedId) override suspend fun insertArticles(articles: List
) = synchronizationDao.insertArticles(articles) + override suspend fun insertArticleTags(articlesTags: List) { + synchronizationDao.insertArticlesTags(articlesTags) + } override suspend fun getCategories(): List = synchronizationDao.getAllCategories() diff -r b25358bd9512 -r 825fa5987f3c app/src/main/java/com/geekorum/ttrss/sync/DatabaseService.kt --- a/app/src/main/java/com/geekorum/ttrss/sync/DatabaseService.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/main/java/com/geekorum/ttrss/sync/DatabaseService.kt Sat May 09 22:38:05 2020 -0400 @@ -22,6 +22,7 @@ import com.geekorum.ttrss.data.AccountInfo import com.geekorum.ttrss.data.Article +import com.geekorum.ttrss.data.ArticlesTags import com.geekorum.ttrss.data.Attachment import com.geekorum.ttrss.data.Category import com.geekorum.ttrss.data.Feed @@ -49,6 +50,7 @@ suspend fun getArticle(id: Long): Article? suspend fun getRandomArticleFromFeed(feedId: Long): Article? suspend fun insertArticles(articles: List
) + suspend fun insertArticleTags(articlesTags: List) suspend fun updateArticle(article: Article) suspend fun getLatestArticleId(): Long? suspend fun getLatestArticleIdFromFeed(feedId: Long): Long? diff -r b25358bd9512 -r 825fa5987f3c app/src/main/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorker.kt --- a/app/src/main/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorker.kt Wed May 13 11:26:44 2020 -0400 +++ b/app/src/main/java/com/geekorum/ttrss/sync/workers/CollectNewArticlesWorker.kt Sat May 09 22:38:05 2020 -0400 @@ -32,6 +32,7 @@ import com.geekorum.ttrss.core.CoroutineDispatchersProvider import com.geekorum.ttrss.data.Article import com.geekorum.ttrss.data.ArticleWithAttachments +import com.geekorum.ttrss.data.ArticlesTags import com.geekorum.ttrss.htmlparsers.ImageUrlExtractor import com.geekorum.ttrss.network.ApiService import com.geekorum.ttrss.sync.BackgroundDataUsageManager @@ -156,7 +157,15 @@ } private suspend fun insertArticles(articles: List) { - databaseService.insertArticles(articles.map { it.article }) + val articlesOnly = articles.map { it.article } + databaseService.insertArticles(articlesOnly) + val articlesTags = articlesOnly.flatMap { + val tags = it.tags.split(",").map(String::trim) + tags.map {tag -> + ArticlesTags(it.id, tag) + } + } + databaseService.insertArticleTags(articlesTags) val attachments = articles.flatMap { it.attachments } databaseService.insertAttachments(attachments) }