WIP: Implement DAOs

pull/1323/head
lihenggui 2 years ago
parent 90bf87b293
commit d56f934e12

@ -1,31 +0,0 @@
/*
* Copyright 2022 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
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.RecentSearchQueryDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao
import com.google.samples.apps.nowinandroid.core.database.dao.TopicFtsDao
internal abstract class NiaDatabase : RoomDatabase() {
abstract fun topicDao(): TopicDao
abstract fun newsResourceDao(): NewsResourceDao
abstract fun topicFtsDao(): TopicFtsDao
abstract fun newsResourceFtsDao(): NewsResourceFtsDao
abstract fun recentSearchQueryDao(): RecentSearchQueryDao
}

@ -16,111 +16,124 @@
package com.google.samples.apps.nowinandroid.core.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceEntity
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceTopicCrossRef
import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.Instant
/**
* DAO for [NewsResource] and [NewsResourceEntity] access
*/
@Dao
interface NewsResourceDao {
class NewsResourceDao(db: NiaDatabase, private val dispatcher: CoroutineDispatcher) {
private val query = db.newsResourceQueries
/**
* Fetches news resources that match the query parameters
*/
@Transaction
@Query(
value = """
SELECT * FROM news_resources
WHERE
CASE WHEN :useFilterNewsIds
THEN id IN (:filterNewsIds)
ELSE 1
END
AND
CASE WHEN :useFilterTopicIds
THEN id IN
(
SELECT news_resource_id FROM news_resources_topics
WHERE topic_id IN (:filterTopicIds)
)
ELSE 1
END
ORDER BY publish_date DESC
""",
)
fun getNewsResources(
useFilterTopicIds: Boolean = false,
filterTopicIds: Set<String> = emptySet(),
useFilterNewsIds: Boolean = false,
filterNewsIds: Set<String> = emptySet(),
): Flow<List<PopulatedNewsResource>>
): Flow<List<PopulatedNewsResource>> {
return query.getNewsResources(
useFilterTopicIds = useFilterTopicIds,
filterTopicIds = filterTopicIds,
useFilterNewsIds = useFilterNewsIds,
filterNewsIds = filterNewsIds,
) { id, title, content, url, headerImageUrl, publishDate, type ->
PopulatedNewsResource(
entity = NewsResourceEntity(
id = id,
title = title,
content = content,
url = url,
headerImageUrl = headerImageUrl,
publishDate = Instant.fromEpochMilliseconds(publishDate),
type = type,
),
// TODO Dealing with NewsResources <-> Topics relationship
topics = emptyList(),
)
}
.asFlow()
.mapToList(dispatcher)
}
/**
* Fetches ids of news resources that match the query parameters
*/
@Transaction
@Query(
value = """
SELECT id FROM news_resources
WHERE
CASE WHEN :useFilterNewsIds
THEN id IN (:filterNewsIds)
ELSE 1
END
AND
CASE WHEN :useFilterTopicIds
THEN id IN
(
SELECT news_resource_id FROM news_resources_topics
WHERE topic_id IN (:filterTopicIds)
)
ELSE 1
END
ORDER BY publish_date DESC
""",
)
fun getNewsResourceIds(
useFilterTopicIds: Boolean = false,
filterTopicIds: Set<String> = emptySet(),
useFilterNewsIds: Boolean = false,
filterNewsIds: Set<String> = emptySet(),
): Flow<List<String>>
): Flow<List<String>> {
return query.getNewsResourceIds(
useFilterTopicIds = useFilterTopicIds,
filterTopicIds = filterTopicIds,
useFilterNewsIds = useFilterNewsIds,
filterNewsIds = filterNewsIds,
)
.asFlow()
.mapToList(dispatcher)
}
/**
* Inserts [entities] into the db if they don't exist, and ignores those that do
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertOrIgnoreNewsResources(entities: List<NewsResourceEntity>): List<Long>
suspend fun insertOrIgnoreNewsResources(entities: List<NewsResourceEntity>): List<Long> {
entities.forEach {
query.insertOrIgnoreNewsResource(
id = it.id,
title = it.title,
content = it.content,
url = it.url,
header_image_url = it.headerImageUrl,
publish_date = it.publishDate.toEpochMilliseconds(),
type = it.type,
)
}
// TODO Return the inserted ids
return entities.mapNotNull {
it.id.toLongOrNull()
}
}
/**
* Inserts or updates [newsResourceEntities] in the db under the specified primary keys
*/
@Upsert
suspend fun upsertNewsResources(newsResourceEntities: List<NewsResourceEntity>)
suspend fun upsertNewsResources(newsResourceEntities: List<NewsResourceEntity>) {
newsResourceEntities.forEach {
query.upsertNewsResource(
id = it.id,
title = it.title,
content = it.content,
url = it.url,
header_image_url = it.headerImageUrl,
publish_date = it.publishDate.toEpochMilliseconds(),
type = it.type,
)
}
}
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertOrIgnoreTopicCrossRefEntities(
newsResourceTopicCrossReferences: List<NewsResourceTopicCrossRef>,
)
) {
// TODO Consider removing cross references
// query.insertOrIgnoreNewsResourceTopicCrossRefs(newsResourceTopicCrossReferences)
}
/**
* Deletes rows in the db matching the specified [ids]
*/
@Query(
value = """
DELETE FROM news_resources
WHERE id in (:ids)
""",
)
suspend fun deleteNewsResources(ids: List<String>)
suspend fun deleteNewsResources(ids: List<String>) {
query.deleteNewsResources(ids)
}
}

@ -16,24 +16,36 @@
package com.google.samples.apps.nowinandroid.core.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import app.cash.sqldelight.coroutines.mapToOneNotNull
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsEntity
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
/**
* DAO for [NewsResourceFtsEntity] access.
*/
@Dao
interface NewsResourceFtsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(newsResources: List<NewsResourceFtsEntity>)
class NewsResourceFtsDao(db: NiaDatabase, private val dispatcher: CoroutineDispatcher) {
private val query = db.newsResourceFtsQueries
suspend fun insertAll(newsResources: List<NewsResourceFtsEntity>) {
newsResources.forEach {
query.insert(
news_resource_id = it.newsResourceId,
title = it.title,
content = it.content,
)
}
}
@Query("SELECT newsResourceId FROM newsResourcesFts WHERE newsResourcesFts MATCH :query")
fun searchAllNewsResources(query: String): Flow<List<String>>
fun searchAllNewsResources(query: String): Flow<List<String>> {
return query.searchAllNewsResources(query)
}
@Query("SELECT count(*) FROM newsResourcesFts")
fun getCount(): Flow<Int>
fun getCount(): Flow<Long> {
return query.getCount()
.asFlow()
.mapToOneNotNull(dispatcher)
}
}

@ -16,28 +16,18 @@
package com.google.samples.apps.nowinandroid.core.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.samples.apps.nowinandroid.core.model.data.Topic
/**
* Defines a topic a user may follow.
* It has a many to many relationship with [NewsResourceEntity]
*/
@Entity(
tableName = "topics",
)
data class TopicEntity(
@PrimaryKey
val id: String,
val name: String,
val shortDescription: String,
@ColumnInfo(defaultValue = "")
val longDescription: String,
@ColumnInfo(defaultValue = "")
val url: String,
@ColumnInfo(defaultValue = "")
val imageUrl: String,
)

@ -12,7 +12,7 @@ getNewsResources:
SELECT * FROM news_resource
WHERE
CASE WHEN :useFilterNewsIds
THEN id IN (:filterNewsIds)
THEN id IN :filterNewsIds
ELSE 1
END
AND
@ -20,7 +20,7 @@ WHERE
THEN id IN
(
SELECT news_resource_id FROM news_resources_topics
WHERE topic_id IN (:filterTopicIds)
WHERE topic_id IN :filterTopicIds
)
ELSE 1
END
@ -30,7 +30,7 @@ getNewsResourceIds:
SELECT id FROM news_resource
WHERE
CASE WHEN :useFilterNewsIds
THEN id IN (:filterNewsIds)
THEN id IN :filterNewsIds
ELSE 1
END
AND
@ -38,17 +38,17 @@ WHERE
THEN id IN
(
SELECT news_resource_id FROM news_resources_topics
WHERE topic_id IN (:filterTopicIds)
WHERE topic_id IN :filterTopicIds
)
ELSE 1
END
ORDER BY publish_date DESC;
insertOrIgnoreNewsResources:
insertOrIgnoreNewsResource:
INSERT OR IGNORE INTO news_resource (id, title, content, url, header_image_url, publish_date, type)
VALUES (?, ?, ?, ?, ?, ?, ?);
upsertNewsResources:
upsertNewsResource:
INSERT INTO news_resource (id, title, content, url, header_image_url, publish_date, type)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
@ -65,4 +65,4 @@ VALUES (?, ?);
deleteNewsResources:
DELETE FROM news_resource
WHERE id IN (:ids);
WHERE id IN :ids;

@ -18,11 +18,11 @@ CREATE TRIGGER news_resource_au AFTER UPDATE ON news_resource BEGIN
INSERT INTO news_resource_fts (rowid, news_resource_id, title, content) VALUES (new.rowid, new.id, new.title, new.content);
END;
insertAll:
INSERT INTO news_resource_fts (news_resource_id, title, content) SELECT id, title, content FROM news_resource;
insert:
INSERT INTO news_resource_fts (news_resource_id, title, content) VALUES (:news_resource_id, :title, :content);
searchAllNewsResources:
SELECT news_resource_id FROM news_resource_fts WHERE news_resource_fts MATCH :query;
SELECT news_resource_id FROM news_resource_fts WHERE news_resource_fts.title MATCH :query;
getCount:
SELECT count(*) FROM news_resource_fts;

Loading…
Cancel
Save