Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize fts-database #1268

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2bf1467
Create new function
Jaehwa-Noh Mar 9, 2024
0e422bf
Create NewsResourceFtsDaoTest
Jaehwa-Noh Mar 9, 2024
3a778a9
Create testNewsResourceFtsEntity function.
Jaehwa-Noh Mar 9, 2024
72ad794
Apply testNewsResourceFtsEntity.
Jaehwa-Noh Mar 9, 2024
5824837
Create deleteAll and deleteAllAndInsertAll function.
Jaehwa-Noh Mar 10, 2024
cc5d2a1
Create TopicFtsDaoTest
Jaehwa-Noh Mar 10, 2024
4aee8a2
Make NiaDatabase public to use in core:data test.
Jaehwa-Noh Mar 10, 2024
89e1a9c
Create newsResourceEntitiesTestData.
Jaehwa-Noh Mar 10, 2024
f16cdca
Create topicEntitiesTestData.
Jaehwa-Noh Mar 10, 2024
bd4b431
Change insertAll to deleteAllAndInsertAll.
Jaehwa-Noh Mar 11, 2024
6a4c6bb
Update gradle
Jaehwa-Noh Mar 11, 2024
7ec025d
Create SearchContentsRepositoryTest
Jaehwa-Noh Mar 11, 2024
5bf8c28
Use test module test data.
Jaehwa-Noh Mar 11, 2024
e8fe319
Compare with newsResourceFtsEntities size.
Jaehwa-Noh Mar 11, 2024
804f964
Merge branch 'android:main' into optimize-ftsdatabase
Jaehwa-Noh Mar 11, 2024
ef02acc
Merge branch 'android:main' into optimize-ftsdatabase
Jaehwa-Noh Mar 14, 2024
9bf1845
Merge branch 'android:main' into optimize-ftsdatabase
Jaehwa-Noh Mar 15, 2024
ec3c83f
Merge branch 'android:main' into optimize-ftsdatabase
Jaehwa-Noh Mar 18, 2024
920afda
Merge branch 'android:main' into optimize-ftsdatabase
Jaehwa-Noh Mar 19, 2024
a50f7e8
Fix long line.
Jaehwa-Noh Mar 25, 2024
92e0d23
Merge remote-tracking branch 'origin/optimize-ftsdatabase' into optim…
Jaehwa-Noh Mar 25, 2024
63e28a2
Change test name to clear meaning.
Jaehwa-Noh Mar 25, 2024
c782883
Add information on test data.
Jaehwa-Noh Mar 25, 2024
e8db836
Add document on test class.
Jaehwa-Noh Mar 25, 2024
7786f52
Add document on test class.
Jaehwa-Noh Mar 25, 2024
b6a99e3
Remove testNewsResourceFtsEntity.
Jaehwa-Noh Apr 26, 2024
ab135ec
Create `NewsResourceEntity.asFtsEntity`.
Jaehwa-Noh Apr 26, 2024
7a0395c
Add KDoc on `TopicEntity.asFtsEntity`.
Jaehwa-Noh Apr 26, 2024
64d7e57
Change to one line.
Jaehwa-Noh Apr 26, 2024
7b8e31a
Get test data from `newsResourceEntitiesTestData`.
Jaehwa-Noh Apr 26, 2024
9ca3a8a
Change test name style to `given_when_then`.
Jaehwa-Noh Apr 26, 2024
0798bee
Change test name style to `given_when_then`.
Jaehwa-Noh Apr 26, 2024
81ad8f4
Change test name to `insertAllThreeTimes`.
Jaehwa-Noh Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.library.jacoco)
alias(libs.plugins.nowinandroid.android.hilt)
alias(libs.plugins.nowinandroid.android.room)
id("kotlinx-serialization")
}

android {
namespace = "com.google.samples.apps.nowinandroid.core.data"

defaultConfig {
testInstrumentationRunner =
"com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
}

testOptions {
unitTests {
isIncludeAndroidResources = true
Expand All @@ -43,4 +50,6 @@ dependencies {
testImplementation(libs.kotlinx.serialization.json)
testImplementation(projects.core.datastoreTest)
testImplementation(projects.core.testing)

androidTestImplementation(projects.core.testing)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.samples.apps.nowinandroid.core.data

import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import com.google.samples.apps.nowinandroid.core.data.repository.DefaultSearchContentsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceFtsDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicFtsDao
import com.google.samples.apps.nowinandroid.core.testing.database.newsResourceEntitiesTestData
import com.google.samples.apps.nowinandroid.core.testing.database.topicEntitiesTestData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals

class SearchContentsRepositoryTest {
Jaehwa-Noh marked this conversation as resolved.
Show resolved Hide resolved

private lateinit var newsResourceDao: NewsResourceDao
private lateinit var topicDao: TopicDao
private lateinit var newsResourceFtsDao: NewsResourceFtsDao
private lateinit var topicFtsDao: TopicFtsDao
private lateinit var db: NiaDatabase
private lateinit var searchContentsRepository: SearchContentsRepository

@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(
context,
NiaDatabase::class.java,
).build()
newsResourceDao = db.newsResourceDao()
topicDao = db.topicDao()
newsResourceFtsDao = db.newsResourceFtsDao()
topicFtsDao = db.topicFtsDao()

searchContentsRepository = DefaultSearchContentsRepository(
newsResourceDao = newsResourceDao,
newsResourceFtsDao = newsResourceFtsDao,
topicDao = topicDao,
topicFtsDao = topicFtsDao,
ioDispatcher = Dispatchers.IO,
)
}

@After
fun closeDb() = db.close()

@Test
fun whenPopulateFtsDataTwice_PopulateFtsOnce() = runTest {
allDataPreSetting()
repeat(2) {
searchContentsRepository.populateFtsData()
}
advanceUntilIdle()
assertEquals(7, searchContentsRepository.getSearchContentsCount().first())
}

@Test
fun whenSearchAndroid_ReturnResult() = runTest {
allDataPreSetting()
searchContentsRepository.populateFtsData()

val searchQuery = "Android"
val topicIds = listOf("2")
val newsResourceIds = listOf("1", "2")
assertEquals(
topicIds,
searchContentsRepository
.searchContents(searchQuery = searchQuery)
.first()
.topics
.map { it.id },
)
assertEquals(
newsResourceIds,
searchContentsRepository
.searchContents(searchQuery = searchQuery)
.first()
.newsResources
.map { it.id },
)
}

private suspend fun allDataPreSetting() {
newsResourceDao.upsertNewsResources(newsResourceEntities = newsResourceEntitiesTestData)
topicDao.upsertTopics(entities = topicEntitiesTestData)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ internal class DefaultSearchContentsRepository @Inject constructor(

override suspend fun populateFtsData() {
withContext(ioDispatcher) {
newsResourceFtsDao.insertAll(
newsResourceFtsDao.deleteAllAndInsertAll(
newsResourceDao.getNewsResources(
useFilterTopicIds = false,
useFilterNewsIds = false,
)
.first()
.map(PopulatedNewsResource::asFtsEntity),
)
topicFtsDao.insertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() })
topicFtsDao.deleteAllAndInsertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() })
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.samples.apps.nowinandroid.core.database.dao

import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsEntity
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals

class NewsResourceFtsDaoTest {
Jaehwa-Noh marked this conversation as resolved.
Show resolved Hide resolved

private lateinit var newsResourceFtsDao: NewsResourceFtsDao
private lateinit var db: NiaDatabase

private val newsResourceFtsEntities = listOf(
testNewsResourceFtsEntity(number = "0"),
testNewsResourceFtsEntity(number = "1"),
testNewsResourceFtsEntity(number = "2"),
testNewsResourceFtsEntity(number = "3"),
)

@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(
context,
NiaDatabase::class.java,
).build()
newsResourceFtsDao = db.newsResourceFtsDao()
}

@After
fun closeDb() {
db.close()
}

@Test
fun whenInsertOnce_InsertedNewsResourceEntityOnce() = runTest {
insertAllNewsResourceFtsEntities()
assertEquals(newsResourceFtsEntities.size, newsResourceFtsDao.getCount().first())
}

@Test
fun whenInsertTwice_InsertedNewsResourceEntityTwice() = runTest {
repeat(2) {
newsResourceFtsDao.insertAll(newsResources = newsResourceFtsEntities)
}
assertEquals(newsResourceFtsEntities.size * 2, newsResourceFtsDao.getCount().first())
}

@Test
fun whenDeleteAndInsertAllThreeTimes_InsertedOnce() = runTest {
repeat(3) {
newsResourceFtsDao.deleteAllAndInsertAll(newsResources = newsResourceFtsEntities)
}
assertEquals(newsResourceFtsEntities.size, newsResourceFtsDao.getCount().first())
}

private suspend fun insertAllNewsResourceFtsEntities() =
newsResourceFtsDao.insertAll(newsResources = newsResourceFtsEntities)

private fun testNewsResourceFtsEntity(
number: String,
) = NewsResourceFtsEntity(
newsResourceId = number,
title = "title$number",
content = "content$number",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.samples.apps.nowinandroid.core.database.dao

import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity
import com.google.samples.apps.nowinandroid.core.database.model.asFtsEntity
import com.google.samples.apps.nowinandroid.core.testing.database.topicEntitiesTestData
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals

class TopicFtsDaoTest {

private lateinit var topicFtsDao: TopicFtsDao
private lateinit var db: NiaDatabase

private lateinit var topicFtsEntities: List<TopicFtsEntity>

@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(
context,
NiaDatabase::class.java,
).build()
topicFtsDao = db.topicFtsDao()

topicFtsEntities = topicEntitiesTestData.map {
it.asFtsEntity()
}
}

@After
fun closeDb() {
db.close()
}

@Test
fun whenInsertOnce_InsertedTopicsFtsEntityOnce() = runTest {
Jaehwa-Noh marked this conversation as resolved.
Show resolved Hide resolved
insertAllNewsResourceFtsEntities()
assertEquals(topicFtsEntities.size, topicFtsDao.getCount().first())
}

@Test
fun whenInsertTwice_InsertedNewsResourceEntityTwice() = runTest {
repeat(2) {
topicFtsDao.insertAll(topics = topicFtsEntities)
}
assertEquals(topicFtsEntities.size * 2, topicFtsDao.getCount().first())
}

@Test
fun whenDeleteAndInsertAllThreeTimes_InsertedOnce() = runTest {
repeat(3) {
topicFtsDao.deleteAllAndInsertAll(topics = topicFtsEntities)
}
assertEquals(topicFtsEntities.size, topicFtsDao.getCount().first())
}

private suspend fun insertAllNewsResourceFtsEntities() =
topicFtsDao.insertAll(topics = topicFtsEntities)
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import com.google.samples.apps.nowinandroid.core.database.util.InstantConverter
@TypeConverters(
InstantConverter::class,
)
internal abstract class NiaDatabase : RoomDatabase() {
abstract class NiaDatabase : RoomDatabase() {
abstract fun topicDao(): TopicDao
abstract fun newsResourceDao(): NewsResourceDao
abstract fun topicFtsDao(): TopicFtsDao
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsEntity
import kotlinx.coroutines.flow.Flow

Expand All @@ -36,4 +37,17 @@ interface NewsResourceFtsDao {

@Query("SELECT count(*) FROM newsResourcesFts")
fun getCount(): Flow<Int>

@Query(
"""
DELETE FROM newsResourcesFts
""",
)
suspend fun deleteAll()

@Transaction
suspend fun deleteAllAndInsertAll(newsResources: List<NewsResourceFtsEntity>) {
deleteAll()
insertAll(newsResources = newsResources)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity
import kotlinx.coroutines.flow.Flow

Expand All @@ -36,4 +37,17 @@ interface TopicFtsDao {

@Query("SELECT count(*) FROM topicsFts")
fun getCount(): Flow<Int>

@Query(
"""
DELETE FROM topicsFts
""",
)
suspend fun deleteAll()

@Transaction
suspend fun deleteAllAndInsertAll(topics: List<TopicFtsEntity>) {
deleteAll()
insertAll(topics = topics)
}
}
Loading