diff --git a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt index bc3141cef..fedf2ca02 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.kt @@ -144,10 +144,9 @@ class TaskDao @Inject constructor( internal suspend fun insert(task: Task): Long = taskDao.insert(task) - internal suspend fun fetchTasks(queries: List): List = - taskDao.fetchTasks(queries) + internal suspend fun fetchTasks(query: String): List = taskDao.fetchTasks(query) internal suspend fun getAll(): List = taskDao.getAll() internal suspend fun getActiveTasks(): List = taskDao.getActiveTasks() -} \ No newline at end of file +} diff --git a/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt b/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt index 10f9b5c86..8d3e649f5 100644 --- a/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt +++ b/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt @@ -8,8 +8,6 @@ import org.tasks.data.sql.Field import org.tasks.data.sql.Query import org.tasks.filters.Filter import org.tasks.preferences.QueryPreferences -import org.tasks.time.DateTimeUtils2.currentTimeMillis -import timber.log.Timber suspend fun TaskDao.fetchTasks(preferences: QueryPreferences, filter: Filter): List = fetchTasks(TaskListQuery.getQuery(preferences, filter)) @@ -25,9 +23,7 @@ suspend fun TaskDao.fetchFiltered(filter: Filter): List = fetchFiltered(fi suspend fun TaskDao.fetchFiltered(queryTemplate: String): List { val query = getQuery(queryTemplate, Task.FIELDS) - val start = if (BuildConfig.DEBUG) currentTimeMillis() else 0 val tasks = fetchTasks(query) - Timber.v("%sms: %s", currentTimeMillis() - start, query) return tasks.map(TaskContainer::task) } diff --git a/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt b/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt index ffda932e7..52bdd9d8e 100644 --- a/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt +++ b/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt @@ -271,7 +271,7 @@ internal class TasksWidgetViewFactory( } } - private suspend fun getQuery(filter: Filter): List { + private suspend fun getQuery(filter: Filter): String { subtasksHelper.applySubtasksToWidgetFilter(filter, widgetPreferences) return getQuery(widgetPreferences, filter) } diff --git a/data/src/commonMain/kotlin/org/tasks/data/dao/TaskDao.kt b/data/src/commonMain/kotlin/org/tasks/data/dao/TaskDao.kt index a02c32a1d..334578420 100644 --- a/data/src/commonMain/kotlin/org/tasks/data/dao/TaskDao.kt +++ b/data/src/commonMain/kotlin/org/tasks/data/dao/TaskDao.kt @@ -6,7 +6,6 @@ import androidx.room.Query import androidx.room.RawQuery import androidx.room.RoomRawQuery import androidx.room.Update -import androidx.room.execSQL import co.touchlab.kermit.Logger import org.tasks.IS_DEBUG import org.tasks.data.TaskContainer @@ -18,7 +17,6 @@ import org.tasks.data.entity.Alarm import org.tasks.data.entity.Task import org.tasks.data.sql.Criterion import org.tasks.data.sql.Functions -import org.tasks.data.withTransaction import org.tasks.time.DateTimeUtils2 private const val MAX_TIME = 9999999999999 @@ -110,21 +108,13 @@ FROM ( + "WHERE completed > 0 AND calendarUri IS NOT NULL AND calendarUri != ''") abstract suspend fun clearCompletedCalendarEvents(): Int - open suspend fun fetchTasks(queries: List): List = - database.withTransaction { - val start = if (IS_DEBUG) DateTimeUtils2.currentTimeMillis() else 0 - val last = queries.size - 1 - for (i in 0 until last) { - execSQL(queries[i]) - } - val result = fetchTasks(queries[last]) - Logger.v("TaskDao") { - "${DateTimeUtils2.currentTimeMillis() - start}ms: ${queries.joinToString(";\n")}" - } - result - } - - suspend fun fetchTasks(query: String): List = fetchRaw(RoomRawQuery(query)) + suspend fun fetchTasks(query: String): List { + val start = DateTimeUtils2.currentTimeMillis() + val result = fetchRaw(RoomRawQuery(query)) + val end = DateTimeUtils2.currentTimeMillis() + Logger.v("TaskDao") { "${end - start}ms: ${query.replace(Regex("\\s+"), " ").trim()}" } + return result + } @RawQuery internal abstract suspend fun fetchRaw(query: RoomRawQuery): List diff --git a/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQuery.kt b/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQuery.kt index b90587a55..659e2233b 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQuery.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQuery.kt @@ -24,10 +24,7 @@ object TaskListQuery { field("$CALDAV_METADATA_JOIN.cd_deleted").eq(0)) val JOINS = """ ${Join.left(CaldavTask.TABLE.`as`(CALDAV_METADATA_JOIN), JOIN_CALDAV)} - ${ - Join.left( - CaldavCalendar.TABLE, field("$CALDAV_METADATA_JOIN.cd_calendar").eq( - CaldavCalendar.UUID))} + ${Join.left(CaldavCalendar.TABLE, field("$CALDAV_METADATA_JOIN.cd_calendar").eq(CaldavCalendar.UUID))} ${Join.left(CaldavAccount.TABLE, CaldavCalendar.ACCOUNT.eq(CaldavAccount.UUID))} ${Join.left(Geofence.TABLE, Geofence.TASK.eq(Task.ID))} ${Join.left(Place.TABLE, Place.UID.eq(Geofence.PLACE))} @@ -43,7 +40,7 @@ object TaskListQuery { fun getQuery( preferences: QueryPreferences, filter: Filter, - ): MutableList { + ): String { val start = currentTimeMillis() return when { filter.supportsManualSort() && preferences.isManualSort -> @@ -58,4 +55,4 @@ object TaskListQuery { else -> getNonRecursiveQuery(filter, preferences) }.also { Logger.v("TaskListQuery") { "Building query took ${currentTimeMillis() - start}ms" } } } -} \ No newline at end of file +} diff --git a/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryNonRecursive.kt b/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryNonRecursive.kt index d7381393d..b298d30cf 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryNonRecursive.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryNonRecursive.kt @@ -29,7 +29,7 @@ internal object TaskListQueryNonRecursive { field("tasks.completed > 0").`as`("parentComplete") )).toTypedArray() - fun getNonRecursiveQuery(filter: Filter, preferences: QueryPreferences): MutableList { + fun getNonRecursiveQuery(filter: Filter, preferences: QueryPreferences): String { val joinedQuery = JOINS + if (filter is AstridOrderingFilter) filter.getSqlQuery() else filter.sql!! val sortMode = preferences.sortMode val groupMode = preferences.groupMode @@ -52,10 +52,9 @@ internal object TaskListQueryNonRecursive { else -> "$query GROUP BY ${Task.ID}" } - return mutableListOf( - Query.select(*FIELDS.plus(sortGroup)) - .withQueryTemplate(PermaSql.replacePlaceholdersForQuery(groupedQuery)) - .from(Task.TABLE) - .toString()) + return Query.select(*FIELDS.plus(sortGroup)) + .withQueryTemplate(PermaSql.replacePlaceholdersForQuery(groupedQuery)) + .from(Task.TABLE) + .toString() } } diff --git a/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryRecursive.kt b/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryRecursive.kt index beba92deb..4854568bc 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryRecursive.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/data/TaskListQueryRecursive.kt @@ -5,12 +5,10 @@ import com.todoroo.astrid.core.SortHelper import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.db.Table import org.tasks.data.entity.CaldavTask -import org.tasks.data.entity.Tag import org.tasks.data.entity.Task import org.tasks.data.sql.Criterion import org.tasks.data.sql.Field.Companion.field import org.tasks.data.sql.Join -import org.tasks.data.sql.Query import org.tasks.data.sql.QueryTemplate import org.tasks.filters.CaldavFilter import org.tasks.filters.Filter @@ -19,32 +17,16 @@ import org.tasks.preferences.QueryPreferences internal object TaskListQueryRecursive { private val RECURSIVE = Table("recursive_tasks") private val RECURSIVE_TASK = field("$RECURSIVE.task") - private val FIELDS = - TaskListQuery.FIELDS.plus(listOf( - field("(${ - Query.select(field("group_concat(distinct(tag_uid))")).from(Tag.TABLE).where( - Task.ID.eq(Tag.TASK))} GROUP BY ${Tag.TASK})").`as`("tags"), - field("indent"), - field("sort_group"), - field("children"), - field("primary_sort"), - field("secondary_sort"), - field("parent_complete"), - )).toTypedArray() - private val JOINS = """ - ${Join.inner(RECURSIVE, Task.ID.eq(RECURSIVE_TASK))} - LEFT JOIN (SELECT parent, count(distinct recursive_tasks.task) AS children FROM recursive_tasks GROUP BY parent) AS recursive_children ON recursive_children.parent = tasks._id - ${TaskListQuery.JOINS} - """.trimIndent() private val SUBTASK_QUERY = QueryTemplate() - .join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK))) - .where(activeAndVisible()) + .join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK))) + .where(activeAndVisible()) + .toString() fun getRecursiveQuery( filter: Filter, preferences: QueryPreferences, - ): MutableList { + ): String { val parentQuery = when (filter) { is CaldavFilter -> newCaldavQuery(filter.uuid) else -> PermaSql.replacePlaceholdersForQuery(filter.sql!!) @@ -74,48 +56,104 @@ internal object TaskListQueryRecursive { preferences.sortAscending && sortMode != SortHelper.SORT_GTASKS && sortMode != SortHelper.SORT_CALDAV val subtaskAscending = preferences.subtaskAscending && subtaskMode != SortHelper.SORT_GTASKS && subtaskMode != SortHelper.SORT_CALDAV - val primaryGroupSelector = SortHelper.orderSelectForSortTypeRecursive(groupMode, true) - val primarySortSelect = SortHelper.orderSelectForSortTypeRecursive(sortMode, false) - val subtaskSort = SortHelper.orderSelectForSortTypeRecursive(subtaskMode, false) - val parentCompleted = if (preferences.completedTasksAtBottom) "tasks.completed > 0" else "0" - val completionSort = if (preferences.completedTasksAtBottom) { + val completedAtBottom = preferences.completedTasksAtBottom + val parentCompleted = if (completedAtBottom) "tasks.completed > 0" else "0" + val completionSort = if (completedAtBottom) { "(CASE WHEN tasks.completed > 0 THEN ${SortHelper.orderSelectForSortTypeRecursive(completedMode, false)} ELSE 0 END)" } else { "0" } - val withClause = """ - CREATE TEMPORARY TABLE `recursive_tasks` AS - WITH RECURSIVE recursive_tasks (task, parent_complete, subtask_complete, completion_sort, parent, collapsed, hidden, indent, title, primary_group, primary_sort, secondary_sort, sort_group) AS ( - SELECT tasks._id, $parentCompleted as parent_complete, 0 as subtask_complete, $completionSort as completion_sort, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(tasks.title) AS sort_title, $primaryGroupSelector as primary_group, $primarySortSelect as primary_sort, NULL as secondarySort, ${SortHelper.getSortGroup(groupMode)} + val query = """ + WITH RECURSIVE recursive_tasks AS ( + SELECT + tasks._id AS task, + $parentCompleted AS parent_complete, + 0 AS subtask_complete, + $completionSort AS completion_sort, + 0 AS parent, + tasks.collapsed AS collapsed, + 0 AS hidden, + 0 AS indent, + UPPER(tasks.title) AS sort_title, + ${SortHelper.orderSelectForSortTypeRecursive(groupMode, true)} AS primary_group, + ${SortHelper.orderSelectForSortTypeRecursive(sortMode, false)} AS primary_sort, + NULL as secondary_sort, + ${SortHelper.getSortGroup(groupMode)} AS sort_group FROM tasks ${ if (groupMode == SortHelper.SORT_LIST) { """ - INNER JOIN caldav_tasks on cd_task = tasks._id AND cd_deleted = 0 - INNER JOIN caldav_lists on cd_calendar = cdl_uuid + INNER JOIN caldav_tasks ON cd_task = tasks._id AND cd_deleted = 0 + INNER JOIN caldav_lists ON cd_calendar = cdl_uuid """.trimIndent() } else { "" } } $parentQuery - UNION ALL SELECT tasks._id, recursive_tasks.parent_complete, $parentCompleted as subtask_complete, $completionSort as completion_sort, recursive_tasks.task as parent, tasks.collapsed as collapsed, CASE WHEN recursive_tasks.collapsed > 0 OR recursive_tasks.hidden > 0 THEN 1 ELSE 0 END as hidden, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, recursive_tasks.primary_group as primary_group, recursive_tasks.primary_sort as primary_sort, $subtaskSort as secondary_sort, recursive_tasks.sort_group FROM tasks + UNION ALL SELECT + tasks._id AS task, + recursive_tasks.parent_complete AS parent_complete, + $parentCompleted AS subtask_complete, + $completionSort AS completion_sort, + recursive_tasks.task AS parent, + tasks.collapsed AS collapsed, + CASE WHEN recursive_tasks.collapsed > 0 OR recursive_tasks.hidden > 0 THEN 1 ELSE 0 END AS hidden, + recursive_tasks.indent+1 AS indent, + UPPER(tasks.title) AS sort_title, + recursive_tasks.primary_group AS primary_group, + recursive_tasks.primary_sort AS primary_sort, + ${SortHelper.orderSelectForSortTypeRecursive(subtaskMode, false)} AS secondary_sort, + recursive_tasks.sort_group AS sort_group + FROM tasks $SUBTASK_QUERY - ORDER BY parent_complete ASC, sort_indent DESC, subtask_complete ASC, completion_sort ${if (preferences.completedAscending) "ASC" else "DESC"}, ${SortHelper.orderForGroupTypeRecursive(groupMode, groupAscending)}, ${SortHelper.orderForSortTypeRecursive(sortMode, sortAscending, subtaskMode, subtaskAscending)} - ) SELECT * FROM recursive_tasks - WHERE indent = (SELECT MAX(indent) FROM recursive_tasks as r WHERE r.task = recursive_tasks.task) + ORDER BY + parent_complete, + indent DESC, + subtask_complete, + completion_sort ${if (preferences.completedAscending) "" else "DESC"}, + ${SortHelper.orderForGroupTypeRecursive(groupMode, groupAscending)}, + ${SortHelper.orderForSortTypeRecursive(sortMode, sortAscending, subtaskMode, subtaskAscending)} + ), + numbered_tasks AS ( + SELECT task, ROW_NUMBER() OVER () AS sequence + FROM recursive_tasks + ), + max_indent AS ( + SELECT task, + MAX(recursive_tasks.indent) OVER (PARTITION BY task) AS max_indent + FROM recursive_tasks + ), + child_counts AS ( + SELECT DISTINCT(parent), + COUNT(*) OVER (PARTITION BY parent) AS children + FROM recursive_tasks + WHERE parent > 0 + ) + SELECT + ${TaskListQuery.FIELDS.joinToString(",\n") { it.toStringInSelect() }}, + group_concat(distinct(tag_uid)) AS tags, + indent, + sort_group, + children, + primary_sort, + secondary_sort, + parent_complete + FROM tasks + INNER JOIN numbered_tasks ON tasks._id = numbered_tasks.task + INNER JOIN max_indent ON tasks._id = max_indent.task + INNER JOIN recursive_tasks ON recursive_tasks.task = tasks._id + LEFT JOIN child_counts ON child_counts.parent = tasks._id + LEFT JOIN tags ON tags.task = tasks._id + ${TaskListQuery.JOINS} + WHERE + recursive_tasks.hidden = 0 + AND recursive_tasks.indent = max_indent + GROUP BY tasks._id + ORDER BY sequence """.trimIndent() - return mutableListOf( - "DROP TABLE IF EXISTS `recursive_tasks`", - SortHelper.adjustQueryForFlags(preferences, withClause), - "CREATE INDEX `r_tasks` ON `recursive_tasks` (`task`)", - "CREATE INDEX `r_parents` ON `recursive_tasks` (`parent`)", - Query.select(*FIELDS) - .withQueryTemplate(PermaSql.replacePlaceholdersForQuery("$JOINS WHERE recursive_tasks.hidden = 0")) - .from(Task.TABLE) - .toString(), - ) + return SortHelper.adjustQueryForFlags(preferences, query) } private fun newCaldavQuery(list: String) =