diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4a9c08043..49ef41433 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -200,6 +200,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.room) + implementation(libs.androidx.sqlite) implementation(libs.androidx.appcompat) implementation(libs.markwon) implementation(libs.markwon.editor) diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index 7fd274af5..a088cb253 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -43,15 +43,12 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.room.withTransaction import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import com.google.android.material.appbar.AppBarLayout import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.composethemeadapter.MdcTheme import com.google.android.material.snackbar.Snackbar -import org.tasks.data.sql.Join -import org.tasks.data.sql.QueryTemplate import com.todoroo.andlib.utility.DateUtilities import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.adapter.TaskAdapterProvider @@ -93,15 +90,18 @@ import org.tasks.billing.PurchaseActivity import org.tasks.caldav.BaseCaldavCalendarSettingsActivity import org.tasks.compose.SubscriptionNagBanner import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.data.TaskContainer import org.tasks.data.dao.CaldavDao +import org.tasks.data.dao.TagDataDao import org.tasks.data.db.Database +import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.data.entity.Tag -import org.tasks.data.dao.TagDataDao import org.tasks.data.entity.Task -import org.tasks.data.TaskContainer import org.tasks.data.listSettingsClass +import org.tasks.data.sql.Join +import org.tasks.data.sql.QueryTemplate +import org.tasks.data.withTransaction import org.tasks.databinding.FragmentTaskListBinding -import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.FilterPicker.Companion.newFilterPicker diff --git a/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt b/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt index 8168ab8d8..fd6274653 100644 --- a/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt +++ b/app/src/main/java/com/todoroo/astrid/service/TaskCompleter.kt @@ -5,7 +5,6 @@ import android.content.Context import android.media.AudioAttributes import android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT import android.media.RingtoneManager -import androidx.room.withTransaction import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.gcal.GCalHelper import com.todoroo.astrid.repeats.RepeatTaskHelper @@ -14,6 +13,7 @@ import org.tasks.LocalBroadcastManager import org.tasks.data.dao.CaldavDao import org.tasks.data.db.Database import org.tasks.data.entity.Task +import org.tasks.data.withTransaction import org.tasks.jobs.WorkManager import org.tasks.notifications.NotificationManager import org.tasks.preferences.Preferences diff --git a/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.kt b/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.kt index 9d96d588c..4ecb15aae 100644 --- a/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.kt +++ b/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.kt @@ -1,7 +1,6 @@ package com.todoroo.astrid.service import android.content.Context -import androidx.room.withTransaction import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext @@ -17,6 +16,8 @@ import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.Task +import org.tasks.data.inTransaction +import org.tasks.data.withTransaction import org.tasks.files.FileHelper import org.tasks.location.GeofenceApi import org.tasks.notifications.NotificationManager diff --git a/app/src/main/java/org/tasks/data/ContentProviderDaoBlocking.kt b/app/src/main/java/org/tasks/data/ContentProviderDaoBlocking.kt index abebcfc6a..7c51801ac 100644 --- a/app/src/main/java/org/tasks/data/ContentProviderDaoBlocking.kt +++ b/app/src/main/java/org/tasks/data/ContentProviderDaoBlocking.kt @@ -1,15 +1,19 @@ package org.tasks.data import android.database.Cursor -import androidx.sqlite.db.SupportSQLiteQuery -import org.tasks.data.entity.Task +import android.database.MatrixCursor +import androidx.sqlite.SQLiteStatement import kotlinx.coroutines.runBlocking -import org.tasks.data.dao.ContentProviderDao +import org.tasks.data.dao.Astrid2ContentProviderDao +import org.tasks.data.db.Database import org.tasks.data.entity.TagData +import org.tasks.data.entity.Task import javax.inject.Inject -@Deprecated("use coroutines") -class ContentProviderDaoBlocking @Inject constructor(private val dao: ContentProviderDao) { +class ContentProviderDaoBlocking @Inject constructor( + private val dao: Astrid2ContentProviderDao, + private val database: Database, +) { fun getTagNames(taskId: Long): List = runBlocking { dao.getTagNames(taskId) } @@ -22,9 +26,24 @@ class ContentProviderDaoBlocking @Inject constructor(private val dao: ContentPro dao.tagDataOrderedByName() } - fun getTasks(): Cursor = dao.getTasks() + fun getTasks(): Cursor = runBlocking { rawQuery("SELECT * FROM tasks") } - fun getLists(): Cursor = dao.getLists() + fun getLists(): Cursor = runBlocking { + rawQuery(""" + SELECT caldav_lists.*, caldav_accounts.cda_name + FROM caldav_lists + INNER JOIN caldav_accounts ON cdl_account = cda_uuid + """.trimIndent() + ) + } - fun rawQuery(query: SupportSQLiteQuery): Cursor = dao.rawQuery(query) -} \ No newline at end of file + fun rawQuery(query: String): Cursor = runBlocking { database.rawQuery(query) { it.toCursor() } } +} + +private fun SQLiteStatement.toCursor(): Cursor { + val cursor = MatrixCursor(getColumnNames().toTypedArray()) + while (step()) { + cursor.addRow((0 until getColumnCount()).map { getText(it) }) + } + return cursor +} diff --git a/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt b/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt index d7da1270b..5b0aa740b 100644 --- a/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt +++ b/app/src/main/java/org/tasks/data/TaskDaoExtensions.kt @@ -1,13 +1,12 @@ package org.tasks.data -import androidx.sqlite.db.SimpleSQLiteQuery -import org.tasks.data.sql.Field -import org.tasks.data.sql.Query import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.PermaSql -import org.tasks.data.entity.Task import org.tasks.data.dao.TaskDao import org.tasks.data.db.SuspendDbUtils.eachChunk +import org.tasks.data.entity.Task +import org.tasks.data.sql.Field +import org.tasks.data.sql.Query import org.tasks.preferences.QueryPreferences import org.tasks.time.DateTimeUtils2.currentTimeMillis import timber.log.Timber @@ -30,7 +29,7 @@ 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.sql) + Timber.v("%sms: %s", currentTimeMillis() - start, query) return tasks.map(TaskContainer::task) } @@ -38,13 +37,12 @@ suspend fun TaskDao.count(filter: Filter): Int { val query = getQuery(filter.sql!!, Field.COUNT) val start = if (BuildConfig.DEBUG) currentTimeMillis() else 0 val count = countRaw(query) - Timber.v("%sms: %s", currentTimeMillis() - start, query.sql) + Timber.v("%sms: %s", currentTimeMillis() - start, query) return count } -private fun getQuery(queryTemplate: String, vararg fields: Field): SimpleSQLiteQuery = - SimpleSQLiteQuery( - Query.select(*fields) - .withQueryTemplate(PermaSql.replacePlaceholdersForQuery(queryTemplate)) - .from(Task.TABLE) - .toString()) +private fun getQuery(queryTemplate: String, vararg fields: Field): String = + Query.select(*fields) + .withQueryTemplate(PermaSql.replacePlaceholdersForQuery(queryTemplate)) + .from(Task.TABLE) + .toString() diff --git a/app/src/main/java/org/tasks/db/Migrations.kt b/app/src/main/java/org/tasks/db/Migrations.kt index fed17aa7a..eadafbc83 100644 --- a/app/src/main/java/org/tasks/db/Migrations.kt +++ b/app/src/main/java/org/tasks/db/Migrations.kt @@ -2,13 +2,13 @@ package org.tasks.db import android.content.Context import android.database.sqlite.SQLiteException -import androidx.core.database.getStringOrNull import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.execSQL +import androidx.sqlite.use import org.tasks.R import org.tasks.caldav.FileStorage import org.tasks.data.NO_ORDER -import org.tasks.data.OpenTaskDao.Companion.getLong import org.tasks.data.entity.Alarm.Companion.TYPE_RANDOM import org.tasks.data.entity.Alarm.Companion.TYPE_REL_END import org.tasks.data.entity.Alarm.Companion.TYPE_REL_START @@ -21,8 +21,6 @@ import org.tasks.data.entity.Task import org.tasks.data.entity.Task.Companion.NOTIFY_AFTER_DEADLINE import org.tasks.data.entity.Task.Companion.NOTIFY_AT_DEADLINE import org.tasks.data.entity.Task.Companion.NOTIFY_AT_START -import org.tasks.extensions.getLongOrNull -import org.tasks.extensions.getString import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.Preferences import org.tasks.repeats.RecurrenceUtils.newRecur @@ -34,21 +32,21 @@ import java.util.concurrent.TimeUnit.HOURS object Migrations { private val MIGRATION_35_36: Migration = object : Migration(35, 36) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `tagdata` ADD COLUMN `color` INTEGER DEFAULT -1") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `tagdata` ADD COLUMN `color` INTEGER DEFAULT -1") } } private val MIGRATION_36_37: Migration = object : Migration(36, 37) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `store` ADD COLUMN `deleted` INTEGER DEFAULT 0") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `store` ADD COLUMN `deleted` INTEGER DEFAULT 0") } } private val MIGRATION_37_38: Migration = object : Migration(37, 38) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrate(connection: SQLiteConnection) { try { - db.execSQL("ALTER TABLE `store` ADD COLUMN `value4` TEXT DEFAULT -1") + connection.execSQL("ALTER TABLE `store` ADD COLUMN `value4` TEXT DEFAULT -1") } catch (e: SQLiteException) { Timber.w(e) } @@ -56,263 +54,263 @@ object Migrations { } private val MIGRATION_38_39: Migration = object : Migration(38, 39) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `notification` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `type` INTEGER NOT NULL)") - db.execSQL( + connection.execSQL( "CREATE UNIQUE INDEX `index_notification_task` ON `notification` (`task`)") } } private val MIGRATION_46_47: Migration = object : Migration(46, 47) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `alarms` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `time` INTEGER NOT NULL)") - db.execSQL( + connection.execSQL( "INSERT INTO `alarms` (`task`, `time`) SELECT `task`, `value` FROM `metadata` WHERE `key` = 'alarm' AND `deleted` = 0") - db.execSQL("DELETE FROM `metadata` WHERE `key` = 'alarm'") + connection.execSQL("DELETE FROM `metadata` WHERE `key` = 'alarm'") } } private val MIGRATION_47_48: Migration = object : Migration(47, 48) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `locations` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `radius` INTEGER NOT NULL)") - db.execSQL("INSERT INTO `locations` (`task`, `name`, `latitude`, `longitude`, `radius`) " + connection.execSQL("INSERT INTO `locations` (`task`, `name`, `latitude`, `longitude`, `radius`) " + "SELECT `task`, `value`, `value2`, `value3`, `value4` FROM `metadata` WHERE `key` = 'geofence' AND `deleted` = 0") - db.execSQL("DELETE FROM `metadata` WHERE `key` = 'geofence'") + connection.execSQL("DELETE FROM `metadata` WHERE `key` = 'geofence'") } } private val MIGRATION_48_49: Migration = object : Migration(48, 49) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `tags` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `tag_uid` TEXT, `task_uid` TEXT)") - db.execSQL("INSERT INTO `tags` (`task`, `name`, `tag_uid`, `task_uid`) " + connection.execSQL("INSERT INTO `tags` (`task`, `name`, `tag_uid`, `task_uid`) " + "SELECT `task`, `value`, `value2`, `value3` FROM `metadata` WHERE `key` = 'tags-tag' AND `deleted` = 0") - db.execSQL("DELETE FROM `metadata` WHERE `key` = 'tags-tag'") + connection.execSQL("DELETE FROM `metadata` WHERE `key` = 'tags-tag'") } } private val MIGRATION_49_50: Migration = object : Migration(49, 50) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `google_tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `remote_id` TEXT, `list_id` TEXT, `parent` INTEGER NOT NULL, `indent` INTEGER NOT NULL, `order` INTEGER NOT NULL, `remote_order` INTEGER NOT NULL, `last_sync` INTEGER NOT NULL, `deleted` INTEGER NOT NULL)") - db.execSQL("INSERT INTO `google_tasks` (`task`, `remote_id`, `list_id`, `parent`, `indent`, `order`, `remote_order`, `last_sync`, `deleted`) " + connection.execSQL("INSERT INTO `google_tasks` (`task`, `remote_id`, `list_id`, `parent`, `indent`, `order`, `remote_order`, `last_sync`, `deleted`) " + "SELECT `task`, `value`, `value2`, IFNULL(`value3`, 0), IFNULL(`value4`, 0), IFNULL(`value5`, 0), IFNULL(`value6`, 0), IFNULL(`value7`, 0), IFNULL(`deleted`, 0) FROM `metadata` WHERE `key` = 'gtasks'") - db.execSQL("DROP TABLE IF EXISTS `metadata`") + connection.execSQL("DROP TABLE IF EXISTS `metadata`") } } private val MIGRATION_50_51: Migration = object : Migration(50, 51) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `filters` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `sql` TEXT, `values` TEXT, `criterion` TEXT)") - db.execSQL("INSERT INTO `filters` (`title`, `sql`, `values`, `criterion`) " + connection.execSQL("INSERT INTO `filters` (`title`, `sql`, `values`, `criterion`) " + "SELECT `item`, `value`, `value2`, `value3` FROM `store` WHERE `type` = 'filter' AND `deleted` = 0") - db.execSQL("DELETE FROM `store` WHERE `type` = 'filter'") + connection.execSQL("DELETE FROM `store` WHERE `type` = 'filter'") } } private val MIGRATION_51_52: Migration = object : Migration(51, 52) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `google_task_lists` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `remote_id` TEXT, `title` TEXT, `remote_order` INTEGER NOT NULL, `last_sync` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `color` INTEGER)") - db.execSQL("INSERT INTO `google_task_lists` (`remote_id`, `title`, `remote_order`, `last_sync`, `color`, `deleted`) " + connection.execSQL("INSERT INTO `google_task_lists` (`remote_id`, `title`, `remote_order`, `last_sync`, `color`, `deleted`) " + "SELECT `item`, `value`, `value2`, `value3`, `value4`, `deleted` FROM `store` WHERE `type` = 'gtasks-list'") - db.execSQL("DROP TABLE IF EXISTS `store`") + connection.execSQL("DROP TABLE IF EXISTS `store`") } } private val MIGRATION_52_53: Migration = object : Migration(52, 53) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `tagdata` RENAME TO `tagdata-temp`") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `tagdata` RENAME TO `tagdata-temp`") + connection.execSQL( "CREATE TABLE `tagdata` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `name` TEXT, `color` INTEGER, `tagOrdering` TEXT)") - db.execSQL("INSERT INTO `tagdata` (`remoteId`, `name`, `color`, `tagOrdering`) " + connection.execSQL("INSERT INTO `tagdata` (`remoteId`, `name`, `color`, `tagOrdering`) " + "SELECT `remoteId`, `name`, `color`, `tagOrdering` FROM `tagdata-temp`") - db.execSQL("DROP TABLE `tagdata-temp`") - db.execSQL("ALTER TABLE `userActivity` RENAME TO `userActivity-temp`") - db.execSQL( + connection.execSQL("DROP TABLE `tagdata-temp`") + connection.execSQL("ALTER TABLE `userActivity` RENAME TO `userActivity-temp`") + connection.execSQL( "CREATE TABLE `userActivity` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `message` TEXT, `picture` TEXT, `target_id` TEXT, `created_at` INTEGER)") - db.execSQL("INSERT INTO `userActivity` (`remoteId`, `message`, `picture`, `target_id`, `created_at`) " + connection.execSQL("INSERT INTO `userActivity` (`remoteId`, `message`, `picture`, `target_id`, `created_at`) " + "SELECT `remoteId`, `message`, `picture`, `target_id`, `created_at` FROM `userActivity-temp`") - db.execSQL("DROP TABLE `userActivity-temp`") - db.execSQL("ALTER TABLE `task_attachments` RENAME TO `task_attachments-temp`") - db.execSQL( + connection.execSQL("DROP TABLE `userActivity-temp`") + connection.execSQL("ALTER TABLE `task_attachments` RENAME TO `task_attachments-temp`") + connection.execSQL( "CREATE TABLE `task_attachments` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `task_id` TEXT, `name` TEXT, `path` TEXT, `content_type` TEXT)") - db.execSQL("INSERT INTO `task_attachments` (`remoteId`, `task_id`, `name`, `path`, `content_type`) " + connection.execSQL("INSERT INTO `task_attachments` (`remoteId`, `task_id`, `name`, `path`, `content_type`) " + "SELECT `remoteId`, `task_id`, `name`, `path`, `content_type` FROM `task_attachments-temp`") - db.execSQL("DROP TABLE `task_attachments-temp`") + connection.execSQL("DROP TABLE `task_attachments-temp`") } } private val MIGRATION_53_54: Migration = object : Migration(53, 54) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrate(connection: SQLiteConnection) { // need to drop columns that were removed in the past - db.execSQL("ALTER TABLE `task_list_metadata` RENAME TO `task_list_metadata-temp`") - db.execSQL( + connection.execSQL("ALTER TABLE `task_list_metadata` RENAME TO `task_list_metadata-temp`") + connection.execSQL( "CREATE TABLE `task_list_metadata` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `remoteId` TEXT, `tag_uuid` TEXT, `filter` TEXT, `task_ids` TEXT)") - db.execSQL("INSERT INTO `task_list_metadata` (`remoteId`, `tag_uuid`, `filter`, `task_ids`) " + connection.execSQL("INSERT INTO `task_list_metadata` (`remoteId`, `tag_uuid`, `filter`, `task_ids`) " + "SELECT `remoteId`, `tag_uuid`, `filter`, `task_ids` FROM `task_list_metadata-temp`") - db.execSQL("DROP TABLE `task_list_metadata-temp`") - db.execSQL("ALTER TABLE `tasks` RENAME TO `tasks-temp`") - db.execSQL( + connection.execSQL("DROP TABLE `task_list_metadata-temp`") + connection.execSQL("ALTER TABLE `tasks` RENAME TO `tasks-temp`") + connection.execSQL( "CREATE TABLE `tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `title` TEXT, `importance` INTEGER, `dueDate` INTEGER, `hideUntil` INTEGER, `created` INTEGER, `modified` INTEGER, `completed` INTEGER, `deleted` INTEGER, `notes` TEXT, `estimatedSeconds` INTEGER, `elapsedSeconds` INTEGER, `timerStart` INTEGER, `notificationFlags` INTEGER, `notifications` INTEGER, `lastNotified` INTEGER, `snoozeTime` INTEGER, `recurrence` TEXT, `repeatUntil` INTEGER, `calendarUri` TEXT, `remoteId` TEXT)") - db.execSQL("DROP INDEX `t_rid`") - db.execSQL("CREATE UNIQUE INDEX `t_rid` ON `tasks` (`remoteId`)") - db.execSQL("INSERT INTO `tasks` (`_id`, `title`, `importance`, `dueDate`, `hideUntil`, `created`, `modified`, `completed`, `deleted`, `notes`, `estimatedSeconds`, `elapsedSeconds`, `timerStart`, `notificationFlags`, `notifications`, `lastNotified`, `snoozeTime`, `recurrence`, `repeatUntil`, `calendarUri`, `remoteId`) " + connection.execSQL("DROP INDEX `t_rid`") + connection.execSQL("CREATE UNIQUE INDEX `t_rid` ON `tasks` (`remoteId`)") + connection.execSQL("INSERT INTO `tasks` (`_id`, `title`, `importance`, `dueDate`, `hideUntil`, `created`, `modified`, `completed`, `deleted`, `notes`, `estimatedSeconds`, `elapsedSeconds`, `timerStart`, `notificationFlags`, `notifications`, `lastNotified`, `snoozeTime`, `recurrence`, `repeatUntil`, `calendarUri`, `remoteId`) " + "SELECT `_id`, `title`, `importance`, `dueDate`, `hideUntil`, `created`, `modified`, `completed`, `deleted`, `notes`, `estimatedSeconds`, `elapsedSeconds`, `timerStart`, `notificationFlags`, `notifications`, `lastNotified`, `snoozeTime`, `recurrence`, `repeatUntil`, `calendarUri`, `remoteId` FROM `tasks-temp`") - db.execSQL("DROP TABLE `tasks-temp`") + connection.execSQL("DROP TABLE `tasks-temp`") } } private val MIGRATION_54_58: Migration = object : Migration(54, 58) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_account` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT, `name` TEXT, `url` TEXT, `username` TEXT, `password` TEXT)") - db.execSQL( + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_calendar` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` TEXT, `uuid` TEXT, `name` TEXT, `color` INTEGER NOT NULL, `ctag` TEXT, `url` TEXT)") - db.execSQL( + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `calendar` TEXT, `object` TEXT, `remote_id` TEXT, `etag` TEXT, `last_sync` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `vtodo` TEXT)") } } private val MIGRATION_58_59: Migration = object : Migration(58, 59) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `google_task_accounts` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` TEXT, `error` TEXT)") - db.execSQL("ALTER TABLE `google_task_lists` ADD COLUMN `account` TEXT") - db.execSQL("ALTER TABLE `caldav_account` ADD COLUMN `error` TEXT") + connection.execSQL("ALTER TABLE `google_task_lists` ADD COLUMN `account` TEXT") + connection.execSQL("ALTER TABLE `caldav_account` ADD COLUMN `error` TEXT") } } private val MIGRATION_59_60: Migration = object : Migration(59, 60) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `locations` ADD COLUMN `address` TEXT") - db.execSQL("ALTER TABLE `locations` ADD COLUMN `phone` TEXT") - db.execSQL("ALTER TABLE `locations` ADD COLUMN `url` TEXT") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `locations` ADD COLUMN `address` TEXT") + connection.execSQL("ALTER TABLE `locations` ADD COLUMN `phone` TEXT") + connection.execSQL("ALTER TABLE `locations` ADD COLUMN `url` TEXT") + connection.execSQL( "ALTER TABLE `locations` ADD COLUMN `arrival` INTEGER DEFAULT 1 NOT NULL") - db.execSQL( + connection.execSQL( "ALTER TABLE `locations` ADD COLUMN `departure` INTEGER DEFAULT 0 NOT NULL") - db.execSQL("ALTER TABLE `notification` ADD COLUMN `location` INTEGER") + connection.execSQL("ALTER TABLE `notification` ADD COLUMN `location` INTEGER") } } private val MIGRATION_60_61: Migration = object : Migration(60, 61) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `places` (`place_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `name` TEXT, `address` TEXT, `phone` TEXT, `url` TEXT, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL)") - db.execSQL( + connection.execSQL( "CREATE TABLE IF NOT EXISTS `geofences` (`geofence_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `place` TEXT, `radius` INTEGER NOT NULL, `arrival` INTEGER NOT NULL, `departure` INTEGER NOT NULL)") - db.execSQL( + connection.execSQL( "INSERT INTO `places` (`place_id`, `uid`, `name`, `address`, `phone`, `url`, `latitude`, `longitude`) SELECT `_id`, hex(randomblob(16)), `name`, `address`, `phone`, `url`, `latitude`, `longitude` FROM `locations`") - db.execSQL( + connection.execSQL( "INSERT INTO `geofences` (`geofence_id`, `task`, `place`, `radius`, `arrival`, `departure`) SELECT `_id`, `task`, `uid`, `radius`, `arrival`, `departure` FROM `locations` INNER JOIN `places` ON `_id` = `place_id`") - db.execSQL("DROP TABLE `locations`") + connection.execSQL("DROP TABLE `locations`") } } private val MIGRATION_61_62: Migration = object : Migration(61, 62) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `google_task_accounts` ADD COLUMN `etag` TEXT") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `google_task_accounts` ADD COLUMN `etag` TEXT") } } private val MIGRATION_62_63: Migration = object : Migration(62, 63) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `google_tasks` RENAME TO `gt-temp`") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `google_tasks` RENAME TO `gt-temp`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `google_tasks` (`gt_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gt_task` INTEGER NOT NULL, `gt_remote_id` TEXT, `gt_list_id` TEXT, `gt_parent` INTEGER NOT NULL, `gt_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_order` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, `gt_last_sync` INTEGER NOT NULL, `gt_deleted` INTEGER NOT NULL)") - db.execSQL("INSERT INTO `google_tasks` (`gt_id`, `gt_task`, `gt_remote_id`, `gt_list_id`, `gt_parent`, `gt_remote_parent`, `gt_moved`, `gt_order`, `gt_remote_order`, `gt_last_sync`, `gt_deleted`) " + connection.execSQL("INSERT INTO `google_tasks` (`gt_id`, `gt_task`, `gt_remote_id`, `gt_list_id`, `gt_parent`, `gt_remote_parent`, `gt_moved`, `gt_order`, `gt_remote_order`, `gt_last_sync`, `gt_deleted`) " + "SELECT `_id`, `task`, `remote_id`, `list_id`, `parent`, '', 0, `order`, `remote_order`, `last_sync`, `deleted` FROM `gt-temp`") - db.execSQL("DROP TABLE `gt-temp`") - db.execSQL("UPDATE `google_task_lists` SET `last_sync` = 0") + connection.execSQL("DROP TABLE `gt-temp`") + connection.execSQL("UPDATE `google_task_lists` SET `last_sync` = 0") } } private val MIGRATION_63_64: Migration = object : Migration(63, 64) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav-temp`") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav-temp`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_vtodo` TEXT)") - db.execSQL("INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_vtodo`)" + connection.execSQL("INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_vtodo`)" + "SELECT `_id`, `task`, `calendar`, `object`, `remote_id`, `etag`, `last_sync`, `deleted`, `vtodo` FROM `caldav-temp`") - db.execSQL("DROP TABLE `caldav-temp`") - db.execSQL( + connection.execSQL("DROP TABLE `caldav-temp`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_accounts` (`cda_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cda_uuid` TEXT, `cda_name` TEXT, `cda_url` TEXT, `cda_username` TEXT, `cda_password` TEXT, `cda_error` TEXT)") - db.execSQL("INSERT INTO `caldav_accounts` (`cda_id`, `cda_uuid`, `cda_name`, `cda_url`, `cda_username`, `cda_password`, `cda_error`) " + connection.execSQL("INSERT INTO `caldav_accounts` (`cda_id`, `cda_uuid`, `cda_name`, `cda_url`, `cda_username`, `cda_password`, `cda_error`) " + "SELECT `_id`, `uuid`, `name`, `url`, `username`, `password`, `error` FROM `caldav_account`") - db.execSQL("DROP TABLE `caldav_account`") - db.execSQL( + connection.execSQL("DROP TABLE `caldav_account`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_lists` (`cdl_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cdl_account` TEXT, `cdl_uuid` TEXT, `cdl_name` TEXT, `cdl_color` INTEGER NOT NULL, `cdl_ctag` TEXT, `cdl_url` TEXT, `cdl_icon` INTEGER)") - db.execSQL("INSERT INTO `caldav_lists` (`cdl_id`, `cdl_account`, `cdl_uuid`, `cdl_name`, `cdl_color`, `cdl_ctag`, `cdl_url`) " + connection.execSQL("INSERT INTO `caldav_lists` (`cdl_id`, `cdl_account`, `cdl_uuid`, `cdl_name`, `cdl_color`, `cdl_ctag`, `cdl_url`) " + "SELECT `_id`, `account`, `uuid`, `name`, `color`, `ctag`, `url` FROM caldav_calendar") - db.execSQL("DROP TABLE `caldav_calendar`") - db.execSQL("ALTER TABLE `google_task_accounts` RENAME TO `gta-temp`") - db.execSQL( + connection.execSQL("DROP TABLE `caldav_calendar`") + connection.execSQL("ALTER TABLE `google_task_accounts` RENAME TO `gta-temp`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `google_task_accounts` (`gta_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gta_account` TEXT, `gta_error` TEXT, `gta_etag` TEXT)") - db.execSQL("INSERT INTO `google_task_accounts` (`gta_id`, `gta_account`, `gta_error`, `gta_etag`) " + connection.execSQL("INSERT INTO `google_task_accounts` (`gta_id`, `gta_account`, `gta_error`, `gta_etag`) " + "SELECT `_id`, `account`, `error`, `etag` FROM `gta-temp`") - db.execSQL("DROP TABLE `gta-temp`") - db.execSQL("ALTER TABLE `google_task_lists` RENAME TO `gtl-temp`") - db.execSQL( + connection.execSQL("DROP TABLE `gta-temp`") + connection.execSQL("ALTER TABLE `google_task_lists` RENAME TO `gtl-temp`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `google_task_lists` (`gtl_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gtl_account` TEXT, `gtl_remote_id` TEXT, `gtl_title` TEXT, `gtl_remote_order` INTEGER NOT NULL, `gtl_last_sync` INTEGER NOT NULL, `gtl_color` INTEGER, `gtl_icon` INTEGER)") - db.execSQL("INSERT INTO `google_task_lists` (`gtl_id`, `gtl_account`, `gtl_remote_id`, `gtl_title`, `gtl_remote_order`, `gtl_last_sync`, `gtl_color`) " + connection.execSQL("INSERT INTO `google_task_lists` (`gtl_id`, `gtl_account`, `gtl_remote_id`, `gtl_title`, `gtl_remote_order`, `gtl_last_sync`, `gtl_color`) " + "SELECT `_id`, `account`, `remote_id`, `title`, `remote_order`, `last_sync`, `color` FROM `gtl-temp`") - db.execSQL("DROP TABLE `gtl-temp`") - db.execSQL("ALTER TABLE `filters` ADD COLUMN `f_color` INTEGER") - db.execSQL("ALTER TABLE `filters` ADD COLUMN `f_icon` INTEGER") - db.execSQL("ALTER TABLE `tagdata` ADD COLUMN `td_icon` INTEGER") + connection.execSQL("DROP TABLE `gtl-temp`") + connection.execSQL("ALTER TABLE `filters` ADD COLUMN `f_color` INTEGER") + connection.execSQL("ALTER TABLE `filters` ADD COLUMN `f_icon` INTEGER") + connection.execSQL("ALTER TABLE `tagdata` ADD COLUMN `td_icon` INTEGER") } } private val MIGRATION_64_65: Migration = object : Migration(64, 65) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "ALTER TABLE `caldav_tasks` ADD COLUMN `cd_parent` INTEGER NOT NULL DEFAULT 0") - db.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_remote_parent` TEXT") + connection.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_remote_parent` TEXT") } } private val MIGRATION_65_66: Migration = object : Migration(65, 66) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE UNIQUE INDEX `place_uid` ON `places` (`uid`)") - db.execSQL("CREATE INDEX `geo_task` ON `geofences` (`task`)") - db.execSQL("CREATE INDEX `tag_task` ON `tags` (`task`)") - db.execSQL("CREATE INDEX `gt_list_parent` ON `google_tasks` (`gt_list_id`, `gt_parent`)") - db.execSQL("CREATE INDEX `gt_task` ON `google_tasks` (`gt_task`)") - db.execSQL("CREATE INDEX `cd_calendar_parent` ON `caldav_tasks` (`cd_calendar`, `cd_parent`)") - db.execSQL("CREATE INDEX `cd_task` ON `caldav_tasks` (`cd_task`)") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("CREATE UNIQUE INDEX `place_uid` ON `places` (`uid`)") + connection.execSQL("CREATE INDEX `geo_task` ON `geofences` (`task`)") + connection.execSQL("CREATE INDEX `tag_task` ON `tags` (`task`)") + connection.execSQL("CREATE INDEX `gt_list_parent` ON `google_tasks` (`gt_list_id`, `gt_parent`)") + connection.execSQL("CREATE INDEX `gt_task` ON `google_tasks` (`gt_task`)") + connection.execSQL("CREATE INDEX `cd_calendar_parent` ON `caldav_tasks` (`cd_calendar`, `cd_parent`)") + connection.execSQL("CREATE INDEX `cd_task` ON `caldav_tasks` (`cd_task`)") } } private val MIGRATION_66_67: Migration = object : Migration(66, 67) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "ALTER TABLE `caldav_accounts` ADD COLUMN `cda_repeat` INTEGER NOT NULL DEFAULT 0") } } private val MIGRATION_67_68: Migration = object : Migration(67, 68) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE INDEX `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)") } } private val MIGRATION_68_69: Migration = object : Migration(68, 69) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "ALTER TABLE `tasks` ADD COLUMN `collapsed` INTEGER NOT NULL DEFAULT 0") } } private val MIGRATION_69_70: Migration = object : Migration(69, 70) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `tasks` ADD COLUMN `parent` INTEGER NOT NULL DEFAULT 0") - db.execSQL("ALTER TABLE `tasks` ADD COLUMN `parent_uuid` TEXT") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `tasks` ADD COLUMN `parent` INTEGER NOT NULL DEFAULT 0") + connection.execSQL("ALTER TABLE `tasks` ADD COLUMN `parent_uuid` TEXT") + connection.execSQL( "UPDATE `tasks` SET `parent` = IFNULL((" + " SELECT p.cd_task FROM caldav_tasks" + " INNER JOIN caldav_tasks AS p ON p.cd_remote_id = caldav_tasks.cd_remote_parent" @@ -320,133 +318,133 @@ object Migrations { + " AND caldav_tasks.cd_deleted = 0" + " AND p.cd_calendar = caldav_tasks.cd_calendar" + " AND p.cd_deleted = 0), 0)") - db.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`") - db.execSQL( + connection.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_vtodo` TEXT, `cd_remote_parent` TEXT)") - db.execSQL("INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_vtodo`, `cd_remote_parent`) " + connection.execSQL("INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_vtodo`, `cd_remote_parent`) " + "SELECT `cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_vtodo`, `cd_remote_parent` FROM `caldav_tasks-temp`") - db.execSQL("DROP TABLE `caldav_tasks-temp`") - db.execSQL("CREATE INDEX `cd_task` ON `caldav_tasks` (`cd_task`)") + connection.execSQL("DROP TABLE `caldav_tasks-temp`") + connection.execSQL("CREATE INDEX `cd_task` ON `caldav_tasks` (`cd_task`)") } } private val MIGRATION_70_71: Migration = object : Migration(70, 71) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `caldav_accounts` ADD COLUMN `cda_encryption_key` TEXT") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `caldav_accounts` ADD COLUMN `cda_encryption_key` TEXT") + connection.execSQL( "ALTER TABLE `caldav_accounts` ADD COLUMN `cda_account_type` INTEGER NOT NULL DEFAULT 0") } } private val MIGRATION_71_72: Migration = object : Migration(71, 72) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "ALTER TABLE `caldav_accounts` ADD COLUMN `cda_collapsed` INTEGER NOT NULL DEFAULT 0") - db.execSQL( + connection.execSQL( "ALTER TABLE `google_task_accounts` ADD COLUMN `gta_collapsed` INTEGER NOT NULL DEFAULT 0") } } private val MIGRATION_72_73: Migration = object : Migration(72, 73) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `places` ADD COLUMN `place_color` INTEGER NOT NULL DEFAULT 0") - db.execSQL("ALTER TABLE `places` ADD COLUMN `place_icon` INTEGER NOT NULL DEFAULT -1") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `places` ADD COLUMN `place_color` INTEGER NOT NULL DEFAULT 0") + connection.execSQL("ALTER TABLE `places` ADD COLUMN `place_icon` INTEGER NOT NULL DEFAULT -1") } } private val MIGRATION_73_74: Migration = object : Migration(73, 74) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `tasks` RENAME TO `tasks-temp`") - db.execSQL("DROP INDEX `t_rid`") - db.execSQL("DROP INDEX `active_and_visible`") - db.execSQL("CREATE TABLE IF NOT EXISTS `tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `importance` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `hideUntil` INTEGER NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `notes` TEXT, `estimatedSeconds` INTEGER NOT NULL, `elapsedSeconds` INTEGER NOT NULL, `timerStart` INTEGER NOT NULL, `notificationFlags` INTEGER NOT NULL, `notifications` INTEGER NOT NULL, `lastNotified` INTEGER NOT NULL, `snoozeTime` INTEGER NOT NULL, `recurrence` TEXT, `repeatUntil` INTEGER NOT NULL, `calendarUri` TEXT, `remoteId` TEXT, `collapsed` INTEGER NOT NULL, `parent` INTEGER NOT NULL, `parent_uuid` TEXT)") - db.execSQL("INSERT INTO `tasks` (`_id`, `title`, `importance`, `dueDate`, `hideUntil`, `created`, `modified`, `completed`, `deleted`, `notes`, `estimatedSeconds`, `elapsedSeconds`, `timerStart`, `notificationFlags`, `notifications`, `lastNotified`, `snoozeTime`, `recurrence`, `repeatUntil`, `calendarUri`, `remoteId`, `collapsed`, `parent`, `parent_uuid`) " + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `tasks` RENAME TO `tasks-temp`") + connection.execSQL("DROP INDEX `t_rid`") + connection.execSQL("DROP INDEX `active_and_visible`") + connection.execSQL("CREATE TABLE IF NOT EXISTS `tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `importance` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `hideUntil` INTEGER NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `notes` TEXT, `estimatedSeconds` INTEGER NOT NULL, `elapsedSeconds` INTEGER NOT NULL, `timerStart` INTEGER NOT NULL, `notificationFlags` INTEGER NOT NULL, `notifications` INTEGER NOT NULL, `lastNotified` INTEGER NOT NULL, `snoozeTime` INTEGER NOT NULL, `recurrence` TEXT, `repeatUntil` INTEGER NOT NULL, `calendarUri` TEXT, `remoteId` TEXT, `collapsed` INTEGER NOT NULL, `parent` INTEGER NOT NULL, `parent_uuid` TEXT)") + connection.execSQL("INSERT INTO `tasks` (`_id`, `title`, `importance`, `dueDate`, `hideUntil`, `created`, `modified`, `completed`, `deleted`, `notes`, `estimatedSeconds`, `elapsedSeconds`, `timerStart`, `notificationFlags`, `notifications`, `lastNotified`, `snoozeTime`, `recurrence`, `repeatUntil`, `calendarUri`, `remoteId`, `collapsed`, `parent`, `parent_uuid`) " + "SELECT `_id`, `title`, IFNULL(`importance`, 3), IFNULL(`dueDate`, 0), IFNULL(`hideUntil`, 0), IFNULL(`created`, 0), IFNULL(`modified`, 0), IFNULL(`completed`, 0), IFNULL(`deleted`, 0), `notes`, IFNULL(`estimatedSeconds`, 0), IFNULL(`elapsedSeconds`, 0), IFNULL(`timerStart`, 0), IFNULL(`notificationFlags`, 0), IFNULL(`notifications`, 0), IFNULL(`lastNotified`, 0), IFNULL(`snoozeTime`, 0), `recurrence`, IFNULL(`repeatUntil`, 0), `calendarUri`, `remoteId`, `collapsed`, IFNULL(`parent`, 0), `parent_uuid` FROM `tasks-temp`") - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `t_rid` ON `tasks` (`remoteId`)") - db.execSQL("CREATE INDEX IF NOT EXISTS `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)") - db.execSQL("DROP TABLE `tasks-temp`") + connection.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `t_rid` ON `tasks` (`remoteId`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)") + connection.execSQL("DROP TABLE `tasks-temp`") } } private val MIGRATION_74_75: Migration = object : Migration(74, 75) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_order` INTEGER") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_order` INTEGER") } } private val MIGRATION_75_76: Migration = object : Migration(75, 76) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `tagdata` ADD COLUMN `td_order` INTEGER NOT NULL DEFAULT $NO_ORDER") - db.execSQL("ALTER TABLE `caldav_lists` ADD COLUMN `cdl_order` INTEGER NOT NULL DEFAULT $NO_ORDER") - db.execSQL("ALTER TABLE `filters` ADD COLUMN `f_order` INTEGER NOT NULL DEFAULT $NO_ORDER") - db.execSQL("ALTER TABLE `places` ADD COLUMN `place_order` INTEGER NOT NULL DEFAULT $NO_ORDER") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `tagdata` ADD COLUMN `td_order` INTEGER NOT NULL DEFAULT $NO_ORDER") + connection.execSQL("ALTER TABLE `caldav_lists` ADD COLUMN `cdl_order` INTEGER NOT NULL DEFAULT $NO_ORDER") + connection.execSQL("ALTER TABLE `filters` ADD COLUMN `f_order` INTEGER NOT NULL DEFAULT $NO_ORDER") + connection.execSQL("ALTER TABLE `places` ADD COLUMN `place_order` INTEGER NOT NULL DEFAULT $NO_ORDER") } } private val MIGRATION_76_77: Migration = object : Migration(76, 77) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "ALTER TABLE `caldav_lists` ADD COLUMN `cdl_access` INTEGER NOT NULL DEFAULT 0") } } private val MIGRATION_77_78: Migration = object : Migration(77, 78) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "CREATE TABLE IF NOT EXISTS `principals` (`principal_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `principal_list` INTEGER NOT NULL, `principal` TEXT, `display_name` TEXT, `invite` INTEGER NOT NULL, `access` INTEGER NOT NULL, FOREIGN KEY(`principal_list`) REFERENCES `caldav_lists`(`cdl_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") - db.execSQL( + connection.execSQL( "CREATE UNIQUE INDEX IF NOT EXISTS `index_principals_principal_list_principal` ON `principals` (`principal_list`, `principal`)" ) } } private val MIGRATION_78_79: Migration = object : Migration(78, 79) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL( "ALTER TABLE `caldav_accounts` ADD COLUMN `cda_server_type` INTEGER NOT NULL DEFAULT $SERVER_UNKNOWN" ) } } private val MIGRATION_79_80 = object : Migration(79, 80) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("DROP TABLE `principals`") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("DROP TABLE `principals`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `principals` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` INTEGER NOT NULL, `href` TEXT NOT NULL, `email` TEXT, `display_name` TEXT, FOREIGN KEY(`account`) REFERENCES `caldav_accounts`(`cda_id`) ON UPDATE NO ACTION ON DELETE CASCADE )" ) - db.execSQL( + connection.execSQL( "CREATE UNIQUE INDEX IF NOT EXISTS `index_principals_account_href` ON `principals` (`account`, `href`)" ) - db.execSQL( + connection.execSQL( "CREATE TABLE IF NOT EXISTS `principal_access` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `principal` INTEGER NOT NULL, `list` INTEGER NOT NULL, `invite` INTEGER NOT NULL, `access` INTEGER NOT NULL, FOREIGN KEY(`principal`) REFERENCES `principals`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`list`) REFERENCES `caldav_lists`(`cdl_id`) ON UPDATE NO ACTION ON DELETE CASCADE )" ) - db.execSQL( + connection.execSQL( "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_access_list_principal` ON `principal_access` (`list`, `principal`)" ) - db.execSQL( + connection.execSQL( "CREATE INDEX IF NOT EXISTS `index_principal_access_principal` ON `principal_access` (`principal`)" ) } } private val MIGRATION_80_81 = object : Migration(80, 81) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `alarms` ADD COLUMN `type` INTEGER NOT NULL DEFAULT 0") - db.execSQL("ALTER TABLE `alarms` ADD COLUMN `repeat` INTEGER NOT NULL DEFAULT 0") - db.execSQL("ALTER TABLE `alarms` ADD COLUMN `interval` INTEGER NOT NULL DEFAULT 0") - db.execSQL( + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `alarms` ADD COLUMN `type` INTEGER NOT NULL DEFAULT 0") + connection.execSQL("ALTER TABLE `alarms` ADD COLUMN `repeat` INTEGER NOT NULL DEFAULT 0") + connection.execSQL("ALTER TABLE `alarms` ADD COLUMN `interval` INTEGER NOT NULL DEFAULT 0") + connection.execSQL( "INSERT INTO `alarms` (`task`, `time`, `type`) SELECT `_id`, 0, $TYPE_REL_START FROM `tasks` WHERE `hideUntil` > 0 AND `notificationFlags` & $NOTIFY_AT_START = $NOTIFY_AT_START" ) - db.execSQL( + connection.execSQL( "INSERT INTO `alarms` (`task`, `time`, `type`) SELECT `_id`, 0, $TYPE_REL_END FROM `tasks` WHERE `dueDate` > 0 AND `notificationFlags` & $NOTIFY_AT_DEADLINE = $NOTIFY_AT_DEADLINE" ) - db.execSQL( + connection.execSQL( "INSERT INTO `alarms` (`task`, `time`, `type`, `repeat`, `interval`) SELECT `_id`, ${HOURS.toMillis(24)}, $TYPE_REL_END, 6, ${HOURS.toMillis(24)} FROM `tasks` WHERE `dueDate` > 0 AND `notificationFlags` & $NOTIFY_AFTER_DEADLINE = $NOTIFY_AFTER_DEADLINE" ) - db.execSQL( + connection.execSQL( "INSERT INTO `alarms` (`task`, `time`, `type`) SELECT `_id`, `notifications`, $TYPE_RANDOM FROM `tasks` WHERE `notifications` > 0" ) - db.execSQL( + connection.execSQL( "INSERT INTO `alarms` (`task`, `time`, `type`) SELECT `_id`, `snoozeTime`, $TYPE_SNOOZE FROM `tasks` WHERE `snoozeTime` > 0" ) } @@ -454,171 +452,176 @@ object Migrations { @Suppress("FunctionName") private fun migration_81_82(fileStorage: FileStorage) = object : Migration(81, 82) { - override fun migrate(db: SupportSQLiteDatabase) { - db - .query("SELECT `cdl_account`, `cd_calendar`, `cd_object`, `cd_vtodo` FROM `caldav_tasks` INNER JOIN `caldav_lists` ON `cdl_uuid` = `cd_calendar`") + override fun migrate(connection: SQLiteConnection) { + connection + .prepare("SELECT `cdl_account`, `cd_calendar`, `cd_object`, `cd_vtodo` FROM `caldav_tasks` INNER JOIN `caldav_lists` ON `cdl_uuid` = `cd_calendar`") .use { - while (it.moveToNext()) { + while (it.step()) { val file = fileStorage.getFile( - it.getString("cdl_account"), - it.getString("cd_calendar"), + it.getText(0), + it.getText(1), ) ?.apply { mkdirs() } ?: continue - val `object` = it.getString("cd_object") ?: continue - fileStorage.write(File(file, `object`), it.getString("cd_vtodo")) + if (it.isNull(2)) continue + val `object` = it.getText(2) + fileStorage.write(File(file, `object`), it.getText(3)) } } - db.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`") - db.execSQL( + connection.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`") + connection.execSQL( "CREATE TABLE IF NOT EXISTS `caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `cd_order` INTEGER)" ) - db.execSQL("DROP INDEX `cd_task`") - db.execSQL("CREATE INDEX IF NOT EXISTS `cd_task` ON `caldav_tasks` (`cd_task`)") - db.execSQL( + connection.execSQL("DROP INDEX `cd_task`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `cd_task` ON `caldav_tasks` (`cd_task`)") + connection.execSQL( "INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `cd_order`) SELECT `cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `cd_order` FROM `caldav_tasks-temp`" ) - db.execSQL("DROP TABLE `caldav_tasks-temp`") + connection.execSQL("DROP TABLE `caldav_tasks-temp`") } } private val MIGRATION_82_83 = object : Migration(82, 83) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `places` ADD COLUMN `radius` INTEGER NOT NULL DEFAULT 250") - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_alarms` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `time` INTEGER NOT NULL, `type` INTEGER NOT NULL DEFAULT 0, `repeat` INTEGER NOT NULL DEFAULT 0, `interval` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") - db.execSQL("INSERT INTO `_new_alarms` (`task`,`repeat`,`interval`,`_id`,`time`,`type`) SELECT `task`,`repeat`,`interval`,`alarms`.`_id`,`time`,`type` FROM `alarms` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") - db.execSQL("DROP TABLE `alarms`") - db.execSQL("ALTER TABLE `_new_alarms` RENAME TO `alarms`") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_alarms_task` ON `alarms` (`task`)") - - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_google_tasks` (`gt_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gt_task` INTEGER NOT NULL, `gt_remote_id` TEXT, `gt_list_id` TEXT, `gt_parent` INTEGER NOT NULL, `gt_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_order` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, `gt_last_sync` INTEGER NOT NULL, `gt_deleted` INTEGER NOT NULL, FOREIGN KEY(`gt_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") - db.execSQL("INSERT INTO `_new_google_tasks` (`gt_parent`,`gt_task`,`gt_remote_parent`,`gt_order`,`gt_last_sync`,`gt_id`,`gt_remote_id`,`gt_list_id`,`gt_moved`,`gt_remote_order`,`gt_deleted`) SELECT `gt_parent`,`gt_task`,`gt_remote_parent`,`gt_order`,`gt_last_sync`,`gt_id`,`gt_remote_id`,`gt_list_id`,`gt_moved`,`gt_remote_order`,`gt_deleted` FROM `google_tasks` INNER JOIN `tasks` ON `tasks`.`_id` = `gt_task`") - db.execSQL("DROP TABLE `google_tasks`") - db.execSQL("ALTER TABLE `_new_google_tasks` RENAME TO `google_tasks`") - db.execSQL("CREATE INDEX IF NOT EXISTS `gt_list_parent` ON `google_tasks` (`gt_list_id`, `gt_parent`)") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_google_tasks_gt_task` ON `google_tasks` (`gt_task`)") - - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_tags` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `tag_uid` TEXT, `task_uid` TEXT, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") - db.execSQL("INSERT INTO `_new_tags` (`task`,`task_uid`,`name`,`tag_uid`,`_id`) SELECT `task`,`task_uid`,`name`,`tag_uid`,`tags`.`_id` FROM `tags` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") - db.execSQL("DROP TABLE `tags`") - db.execSQL("ALTER TABLE `_new_tags` RENAME TO `tags`") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_tags_task` ON `tags` (`task`)") - - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_notification` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `type` INTEGER NOT NULL, `location` INTEGER, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") - db.execSQL("INSERT INTO `_new_notification` (`uid`,`task`,`location`,`type`,`timestamp`) SELECT `uid`,`task`,`location`,`type`,`timestamp` FROM `notification` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") - db.execSQL("DROP TABLE `notification`") - db.execSQL("ALTER TABLE `_new_notification` RENAME TO `notification`") - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_notification_task` ON `notification` (`task`)") - - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `cd_order` INTEGER, FOREIGN KEY(`cd_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") - db.execSQL("INSERT INTO `_new_caldav_tasks` (`cd_object`,`cd_deleted`,`cd_order`,`cd_remote_parent`,`cd_etag`,`cd_id`,`cd_calendar`,`cd_remote_id`,`cd_last_sync`,`cd_task`) SELECT `cd_object`,`cd_deleted`,`cd_order`,`cd_remote_parent`,`cd_etag`,`cd_id`,`cd_calendar`,`cd_remote_id`,`cd_last_sync`,`cd_task` FROM `caldav_tasks` INNER JOIN `tasks` ON `tasks`.`_id` = `cd_task`") - db.execSQL("DROP TABLE `caldav_tasks`") - db.execSQL("ALTER TABLE `_new_caldav_tasks` RENAME TO `caldav_tasks`") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_caldav_tasks_cd_task` ON `caldav_tasks` (`cd_task`)") - - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_geofences` (`geofence_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `place` TEXT, `arrival` INTEGER NOT NULL, `departure` INTEGER NOT NULL, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") - db.execSQL("INSERT INTO `_new_geofences` (`task`,`geofence_id`,`arrival`,`place`,`departure`) SELECT `task`,`geofence_id`,`arrival`,`place`,`departure` FROM `geofences` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") - db.execSQL("DROP TABLE `geofences`") - db.execSQL("ALTER TABLE `_new_geofences` RENAME TO `geofences`") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_geofences_task` ON `geofences` (`task`)") - - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_task_list_metadata` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `tag_uuid` TEXT, `filter` TEXT, `task_ids` TEXT)") - db.execSQL("INSERT INTO `_new_task_list_metadata` (`filter`,`tag_uuid`,`_id`,`task_ids`) SELECT `filter`,`tag_uuid`,`_id`,`task_ids` FROM `task_list_metadata`") - db.execSQL("DROP TABLE `task_list_metadata`") - db.execSQL("ALTER TABLE `_new_task_list_metadata` RENAME TO `task_list_metadata`") - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `importance` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `hideUntil` INTEGER NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `notes` TEXT, `estimatedSeconds` INTEGER NOT NULL, `elapsedSeconds` INTEGER NOT NULL, `timerStart` INTEGER NOT NULL, `notificationFlags` INTEGER NOT NULL, `lastNotified` INTEGER NOT NULL, `recurrence` TEXT, `repeatUntil` INTEGER NOT NULL, `calendarUri` TEXT, `remoteId` TEXT, `collapsed` INTEGER NOT NULL, `parent` INTEGER NOT NULL)") - db.execSQL("INSERT INTO `_new_tasks` (`parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`repeatUntil`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`) SELECT `parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`repeatUntil`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds` FROM `tasks`") - db.execSQL("DROP TABLE `tasks`") - db.execSQL("ALTER TABLE `_new_tasks` RENAME TO `tasks`") - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `t_rid` ON `tasks` (`remoteId`)") - db.execSQL("CREATE INDEX IF NOT EXISTS `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `places` ADD COLUMN `radius` INTEGER NOT NULL DEFAULT 250") + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_alarms` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `time` INTEGER NOT NULL, `type` INTEGER NOT NULL DEFAULT 0, `repeat` INTEGER NOT NULL DEFAULT 0, `interval` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") + connection.execSQL("INSERT INTO `_new_alarms` (`task`,`repeat`,`interval`,`_id`,`time`,`type`) SELECT `task`,`repeat`,`interval`,`alarms`.`_id`,`time`,`type` FROM `alarms` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") + connection.execSQL("DROP TABLE `alarms`") + connection.execSQL("ALTER TABLE `_new_alarms` RENAME TO `alarms`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_alarms_task` ON `alarms` (`task`)") + + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_google_tasks` (`gt_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gt_task` INTEGER NOT NULL, `gt_remote_id` TEXT, `gt_list_id` TEXT, `gt_parent` INTEGER NOT NULL, `gt_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_order` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, `gt_last_sync` INTEGER NOT NULL, `gt_deleted` INTEGER NOT NULL, FOREIGN KEY(`gt_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") + connection.execSQL("INSERT INTO `_new_google_tasks` (`gt_parent`,`gt_task`,`gt_remote_parent`,`gt_order`,`gt_last_sync`,`gt_id`,`gt_remote_id`,`gt_list_id`,`gt_moved`,`gt_remote_order`,`gt_deleted`) SELECT `gt_parent`,`gt_task`,`gt_remote_parent`,`gt_order`,`gt_last_sync`,`gt_id`,`gt_remote_id`,`gt_list_id`,`gt_moved`,`gt_remote_order`,`gt_deleted` FROM `google_tasks` INNER JOIN `tasks` ON `tasks`.`_id` = `gt_task`") + connection.execSQL("DROP TABLE `google_tasks`") + connection.execSQL("ALTER TABLE `_new_google_tasks` RENAME TO `google_tasks`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `gt_list_parent` ON `google_tasks` (`gt_list_id`, `gt_parent`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_google_tasks_gt_task` ON `google_tasks` (`gt_task`)") + + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_tags` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `name` TEXT, `tag_uid` TEXT, `task_uid` TEXT, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") + connection.execSQL("INSERT INTO `_new_tags` (`task`,`task_uid`,`name`,`tag_uid`,`_id`) SELECT `task`,`task_uid`,`name`,`tag_uid`,`tags`.`_id` FROM `tags` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") + connection.execSQL("DROP TABLE `tags`") + connection.execSQL("ALTER TABLE `_new_tags` RENAME TO `tags`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_tags_task` ON `tags` (`task`)") + + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_notification` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `type` INTEGER NOT NULL, `location` INTEGER, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") + connection.execSQL("INSERT INTO `_new_notification` (`uid`,`task`,`location`,`type`,`timestamp`) SELECT `uid`,`task`,`location`,`type`,`timestamp` FROM `notification` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") + connection.execSQL("DROP TABLE `notification`") + connection.execSQL("ALTER TABLE `_new_notification` RENAME TO `notification`") + connection.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_notification_task` ON `notification` (`task`)") + + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `cd_order` INTEGER, FOREIGN KEY(`cd_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") + connection.execSQL("INSERT INTO `_new_caldav_tasks` (`cd_object`,`cd_deleted`,`cd_order`,`cd_remote_parent`,`cd_etag`,`cd_id`,`cd_calendar`,`cd_remote_id`,`cd_last_sync`,`cd_task`) SELECT `cd_object`,`cd_deleted`,`cd_order`,`cd_remote_parent`,`cd_etag`,`cd_id`,`cd_calendar`,`cd_remote_id`,`cd_last_sync`,`cd_task` FROM `caldav_tasks` INNER JOIN `tasks` ON `tasks`.`_id` = `cd_task`") + connection.execSQL("DROP TABLE `caldav_tasks`") + connection.execSQL("ALTER TABLE `_new_caldav_tasks` RENAME TO `caldav_tasks`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_caldav_tasks_cd_task` ON `caldav_tasks` (`cd_task`)") + + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_geofences` (`geofence_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER NOT NULL, `place` TEXT, `arrival` INTEGER NOT NULL, `departure` INTEGER NOT NULL, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )") + connection.execSQL("INSERT INTO `_new_geofences` (`task`,`geofence_id`,`arrival`,`place`,`departure`) SELECT `task`,`geofence_id`,`arrival`,`place`,`departure` FROM `geofences` INNER JOIN `tasks` ON `tasks`.`_id` = `task`") + connection.execSQL("DROP TABLE `geofences`") + connection.execSQL("ALTER TABLE `_new_geofences` RENAME TO `geofences`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_geofences_task` ON `geofences` (`task`)") + + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_task_list_metadata` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `tag_uuid` TEXT, `filter` TEXT, `task_ids` TEXT)") + connection.execSQL("INSERT INTO `_new_task_list_metadata` (`filter`,`tag_uuid`,`_id`,`task_ids`) SELECT `filter`,`tag_uuid`,`_id`,`task_ids` FROM `task_list_metadata`") + connection.execSQL("DROP TABLE `task_list_metadata`") + connection.execSQL("ALTER TABLE `_new_task_list_metadata` RENAME TO `task_list_metadata`") + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `importance` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `hideUntil` INTEGER NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `notes` TEXT, `estimatedSeconds` INTEGER NOT NULL, `elapsedSeconds` INTEGER NOT NULL, `timerStart` INTEGER NOT NULL, `notificationFlags` INTEGER NOT NULL, `lastNotified` INTEGER NOT NULL, `recurrence` TEXT, `repeatUntil` INTEGER NOT NULL, `calendarUri` TEXT, `remoteId` TEXT, `collapsed` INTEGER NOT NULL, `parent` INTEGER NOT NULL)") + connection.execSQL("INSERT INTO `_new_tasks` (`parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`repeatUntil`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`) SELECT `parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`repeatUntil`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds` FROM `tasks`") + connection.execSQL("DROP TABLE `tasks`") + connection.execSQL("ALTER TABLE `_new_tasks` RENAME TO `tasks`") + connection.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `t_rid` ON `tasks` (`remoteId`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)") } } private val MIGRATION_84_85 = object : Migration(84, 85) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `tasks` ADD COLUMN `repeat_from` INTEGER NOT NULL DEFAULT ${Task.RepeatFrom.DUE_DATE}") - db - .query("SELECT `_id`, `repeatUntil`, `recurrence` FROM `tasks` WHERE `recurrence` IS NOT NULL AND `recurrence` != ''") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `tasks` ADD COLUMN `repeat_from` INTEGER NOT NULL DEFAULT ${Task.RepeatFrom.DUE_DATE}") + connection + .prepare("SELECT `_id`, `repeatUntil`, `recurrence` FROM `tasks` WHERE `recurrence` IS NOT NULL AND `recurrence` != ''") .use { cursor -> - while (cursor.moveToNext()) { - val id = cursor.getLong("_id") - val recurrence = - cursor.getString("recurrence")?.takeIf { it.isNotBlank() } ?: continue + while (cursor.step()) { + val id = cursor.getLong(0) + val recurrence = if (!cursor.isNull(2)) { + cursor.getText(2).takeIf { it.isNotBlank() } + } else { + null + } ?: continue val recur = newRecur(recurrence.withoutFrom()!!) - cursor.getLongOrNull("repeatUntil") - ?.takeIf { it > 0 } - ?.let { recur.until = DateTime(it).toDate() } + if (!cursor.isNull(1)) { + cursor.getLong(1).takeIf { it > 0 }?.let { recur.until = DateTime(it).toDate() } + } val repeatFrom = recurrence.repeatFrom() - db.execSQL("UPDATE `tasks` SET `repeat_from` = $repeatFrom, `recurrence` = '$recur' WHERE `_id` = $id") + connection.execSQL("UPDATE `tasks` SET `repeat_from` = $repeatFrom, `recurrence` = '$recur' WHERE `_id` = $id") } } - db.execSQL("CREATE TABLE IF NOT EXISTS `_new_tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `importance` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `hideUntil` INTEGER NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `notes` TEXT, `estimatedSeconds` INTEGER NOT NULL, `elapsedSeconds` INTEGER NOT NULL, `timerStart` INTEGER NOT NULL, `notificationFlags` INTEGER NOT NULL, `lastNotified` INTEGER NOT NULL, `recurrence` TEXT, `repeat_from` INTEGER NOT NULL DEFAULT 0, `calendarUri` TEXT, `remoteId` TEXT, `collapsed` INTEGER NOT NULL, `parent` INTEGER NOT NULL)") - db.execSQL("INSERT INTO `_new_tasks` (`parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`,`repeat_from`) SELECT `parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`,`repeat_from` FROM `tasks`") - db.execSQL("DROP TABLE `tasks`") - db.execSQL("ALTER TABLE `_new_tasks` RENAME TO `tasks`") - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `t_rid` ON `tasks` (`remoteId`)") - db.execSQL("CREATE INDEX IF NOT EXISTS `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)") + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `importance` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `hideUntil` INTEGER NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `notes` TEXT, `estimatedSeconds` INTEGER NOT NULL, `elapsedSeconds` INTEGER NOT NULL, `timerStart` INTEGER NOT NULL, `notificationFlags` INTEGER NOT NULL, `lastNotified` INTEGER NOT NULL, `recurrence` TEXT, `repeat_from` INTEGER NOT NULL DEFAULT 0, `calendarUri` TEXT, `remoteId` TEXT, `collapsed` INTEGER NOT NULL, `parent` INTEGER NOT NULL)") + connection.execSQL("INSERT INTO `_new_tasks` (`parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`,`repeat_from`) SELECT `parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`,`repeat_from` FROM `tasks`") + connection.execSQL("DROP TABLE `tasks`") + connection.execSQL("ALTER TABLE `_new_tasks` RENAME TO `tasks`") + connection.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `t_rid` ON `tasks` (`remoteId`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)") } } private val MIGRATION_85_86 = object : Migration(85, 86) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS `attachment_file` (`file_id` INTEGER PRIMARY KEY AUTOINCREMENT, `file_uuid` TEXT NOT NULL, `filename` TEXT NOT NULL, `uri` TEXT NOT NULL)") - db.execSQL("INSERT INTO `attachment_file` (`file_id`, `uri`,`filename`,`file_id`,`file_uuid`) SELECT `_id`, `path`,`name`,`_id`,`remoteId` FROM `task_attachments`") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("CREATE TABLE IF NOT EXISTS `attachment_file` (`file_id` INTEGER PRIMARY KEY AUTOINCREMENT, `file_uuid` TEXT NOT NULL, `filename` TEXT NOT NULL, `uri` TEXT NOT NULL)") + connection.execSQL("INSERT INTO `attachment_file` (`file_id`, `uri`,`filename`,`file_id`,`file_uuid`) SELECT `_id`, `path`,`name`,`_id`,`remoteId` FROM `task_attachments`") - db.execSQL("CREATE TABLE IF NOT EXISTS `attachment` (`attachment_id` INTEGER PRIMARY KEY AUTOINCREMENT, `task` INTEGER NOT NULL, `file` INTEGER NOT NULL, `file_uuid` TEXT NOT NULL, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`file`) REFERENCES `attachment_file`(`file_id`) ON UPDATE NO ACTION ON DELETE CASCADE)") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_attachment_task` ON `attachment` (`task`)") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_attachment_file` ON `attachment` (`file`)") - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_attachment_task_file` ON `attachment` (`task`, `file`)") + connection.execSQL("CREATE TABLE IF NOT EXISTS `attachment` (`attachment_id` INTEGER PRIMARY KEY AUTOINCREMENT, `task` INTEGER NOT NULL, `file` INTEGER NOT NULL, `file_uuid` TEXT NOT NULL, FOREIGN KEY(`task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`file`) REFERENCES `attachment_file`(`file_id`) ON UPDATE NO ACTION ON DELETE CASCADE)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_attachment_task` ON `attachment` (`task`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_attachment_file` ON `attachment` (`file`)") + connection.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_attachment_task_file` ON `attachment` (`task`, `file`)") - db.execSQL( + connection.execSQL( "INSERT INTO `attachment` (`task`, `file`, `file_uuid`) SELECT `tasks`.`_id`, `task_attachments`.`_id`, `task_attachments`.`remoteId` FROM `task_attachments` JOIN `tasks` ON `tasks`.`remoteId` = `task_attachments`.`task_id`" ) - db.execSQL("DROP TABLE `task_attachments`") + connection.execSQL("DROP TABLE `task_attachments`") } } private val MIGRATION_86_87 = object : Migration(86, 87) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE `tasks` ADD COLUMN `read_only` INTEGER NOT NULL DEFAULT 0") - db.execSQL("UPDATE `tasks` SET `read_only` = 1 WHERE `_id` IN (SELECT `cd_task` FROM `caldav_tasks` INNER JOIN `caldav_lists` ON `caldav_tasks`.`cd_calendar` = `caldav_lists`.`cdl_uuid` WHERE `cdl_access` = $ACCESS_READ_ONLY)") + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE `tasks` ADD COLUMN `read_only` INTEGER NOT NULL DEFAULT 0") + connection.execSQL("UPDATE `tasks` SET `read_only` = 1 WHERE `_id` IN (SELECT `cd_task` FROM `caldav_tasks` INNER JOIN `caldav_lists` ON `caldav_tasks`.`cd_calendar` = `caldav_lists`.`cdl_uuid` WHERE `cdl_access` = $ACCESS_READ_ONLY)") } } private fun migration_87_88(context: Context) = object : Migration(87, 88) { - override fun migrate(db: SupportSQLiteDatabase) { + override fun migrate(connection: SQLiteConnection) { val prefs = Preferences(context) val defaultList = prefs.getStringValue(R.string.p_default_list)?.split(":") if ( (defaultList?.size == 2) && (defaultList[0].toIntOrNull()?.equals(DefaultFilterProvider.TYPE_GOOGLE_TASKS) == true) ) { - db.query("SELECT `gtl_remote_id` FROM `google_task_lists` WHERE `gtl_id` = '${defaultList[1]}'").use { cursor -> - if (cursor.moveToFirst()) { - cursor.getStringOrNull(0)?.let { uuid -> + connection.prepare("SELECT `gtl_remote_id` FROM `google_task_lists` WHERE `gtl_id` = '${defaultList[1]}'").use { cursor -> + if (cursor.step()) { + if (!cursor.isNull(0)) { + val uuid = cursor.getText(0) prefs.setString(R.string.p_default_list, "${DefaultFilterProvider.TYPE_GOOGLE_TASKS}:$uuid") } } } } // migrate google task accounts and lists to caldav table - db.execSQL("ALTER TABLE `caldav_lists` ADD COLUMN `cdl_last_sync` INTEGER NOT NULL DEFAULT 0") - db.execSQL("INSERT INTO `caldav_accounts` (`cda_account_type`, `cda_server_type`, `cda_uuid`, `cda_name`, `cda_username`, `cda_collapsed`) SELECT $TYPE_GOOGLE_TASKS, $SERVER_UNKNOWN, `gta_account`, `gta_account`, `gta_account`, `gta_collapsed` FROM `google_task_accounts`") - db.execSQL("INSERT INTO `caldav_lists` (`cdl_account`, `cdl_uuid`, `cdl_name`, `cdl_color`, `cdl_icon`, `cdl_order`, `cdl_access`, `cdl_last_sync`) SELECT `gtl_account`, `gtl_remote_id`, `gtl_title`, `gtl_color`, `gtl_icon`, `gtl_remote_order`, $ACCESS_OWNER, `gtl_last_sync` FROM `google_task_lists`") - db.execSQL("DROP TABLE `google_task_accounts`") - db.execSQL("DROP TABLE `google_task_lists`") + connection.execSQL("ALTER TABLE `caldav_lists` ADD COLUMN `cdl_last_sync` INTEGER NOT NULL DEFAULT 0") + connection.execSQL("INSERT INTO `caldav_accounts` (`cda_account_type`, `cda_server_type`, `cda_uuid`, `cda_name`, `cda_username`, `cda_collapsed`) SELECT $TYPE_GOOGLE_TASKS, $SERVER_UNKNOWN, `gta_account`, `gta_account`, `gta_account`, `gta_collapsed` FROM `google_task_accounts`") + connection.execSQL("INSERT INTO `caldav_lists` (`cdl_account`, `cdl_uuid`, `cdl_name`, `cdl_color`, `cdl_icon`, `cdl_order`, `cdl_access`, `cdl_last_sync`) SELECT `gtl_account`, `gtl_remote_id`, `gtl_title`, `gtl_color`, `gtl_icon`, `gtl_remote_order`, $ACCESS_OWNER, `gtl_last_sync` FROM `google_task_lists`") + connection.execSQL("DROP TABLE `google_task_accounts`") + connection.execSQL("DROP TABLE `google_task_lists`") // move cd_order to task table - db.execSQL("ALTER TABLE `tasks` ADD COLUMN `order` INTEGER") - db.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav-temp`") - db.execSQL("CREATE TABLE IF NOT EXISTS `caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, FOREIGN KEY(`cd_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE)") - db.execSQL("DROP INDEX `index_caldav_tasks_cd_task`") - db.execSQL("CREATE INDEX IF NOT EXISTS `index_caldav_tasks_cd_task` ON `caldav_tasks` (`cd_task`)") - db.execSQL("INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `gt_moved`, `gt_remote_order`) SELECT `cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, 0, 0 FROM `caldav-temp`") - db.execSQL("DROP TABLE `caldav-temp`") - db.execSQL("INSERT INTO `caldav_tasks` (`cd_task`, `cd_calendar`, `cd_remote_id`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `gt_moved`, `gt_remote_order`) SELECT `gt_task`, `gt_list_id`, `gt_remote_id`, `gt_last_sync`, `gt_deleted`, `gt_remote_parent`, 0, 0 FROM google_tasks") - db.execSQL("DROP TABLE `google_tasks`") + connection.execSQL("ALTER TABLE `tasks` ADD COLUMN `order` INTEGER") + connection.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav-temp`") + connection.execSQL("CREATE TABLE IF NOT EXISTS `caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, FOREIGN KEY(`cd_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE)") + connection.execSQL("DROP INDEX `index_caldav_tasks_cd_task`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_caldav_tasks_cd_task` ON `caldav_tasks` (`cd_task`)") + connection.execSQL("INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `gt_moved`, `gt_remote_order`) SELECT `cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, 0, 0 FROM `caldav-temp`") + connection.execSQL("DROP TABLE `caldav-temp`") + connection.execSQL("INSERT INTO `caldav_tasks` (`cd_task`, `cd_calendar`, `cd_remote_id`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `gt_moved`, `gt_remote_order`) SELECT `gt_task`, `gt_list_id`, `gt_remote_id`, `gt_last_sync`, `gt_deleted`, `gt_remote_parent`, 0, 0 FROM google_tasks") + connection.execSQL("DROP TABLE `google_tasks`") } } @@ -672,7 +675,7 @@ object Migrations { ) private fun noop(from: Int, to: Int): Migration = object : Migration(from, to) { - override fun migrate(db: SupportSQLiteDatabase) {} + override fun migrate(connection: SQLiteConnection) {} } fun String?.repeatFrom() = if (this?.contains("FROM=COMPLETION") == true) { diff --git a/app/src/main/java/org/tasks/injection/ApplicationModule.kt b/app/src/main/java/org/tasks/injection/ApplicationModule.kt index f908dfc43..3c432a7a4 100644 --- a/app/src/main/java/org/tasks/injection/ApplicationModule.kt +++ b/app/src/main/java/org/tasks/injection/ApplicationModule.kt @@ -18,7 +18,7 @@ import org.tasks.billing.BillingClientImpl import org.tasks.billing.Inventory import org.tasks.data.dao.AlarmDao import org.tasks.data.dao.CaldavDao -import org.tasks.data.dao.ContentProviderDao +import org.tasks.data.dao.Astrid2ContentProviderDao import org.tasks.data.dao.DeletionDao import org.tasks.data.dao.FilterDao import org.tasks.data.dao.GoogleTaskDao @@ -106,7 +106,7 @@ class ApplicationModule { @Provides @Singleton - fun getContentProviderDao(db: Database): ContentProviderDao = db.contentProviderDao() + fun getContentProviderDao(db: Database): Astrid2ContentProviderDao = db.contentProviderDao() @Provides @Singleton diff --git a/app/src/main/java/org/tasks/injection/ProductionModule.kt b/app/src/main/java/org/tasks/injection/ProductionModule.kt index e096b0c61..7de63a7bc 100644 --- a/app/src/main/java/org/tasks/injection/ProductionModule.kt +++ b/app/src/main/java/org/tasks/injection/ProductionModule.kt @@ -2,7 +2,7 @@ package org.tasks.injection import android.content.Context import androidx.room.Room -import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -37,8 +37,8 @@ internal class ProductionModule { context = context, name = databaseFile.absolutePath ) + .setDriver(BundledSQLiteDriver()) .addMigrations(*Migrations.migrations(context, fileStorage)) - .openHelperFactory(FrameworkSQLiteOpenHelperFactory()) if (!BuildConfig.DEBUG || !preferences.getBoolean(R.string.p_crash_main_queries, false)) { builder.allowMainThreadQueries() } diff --git a/app/src/main/java/org/tasks/provider/TasksContentProvider.kt b/app/src/main/java/org/tasks/provider/TasksContentProvider.kt index 4feb23c5f..6defdfdd0 100644 --- a/app/src/main/java/org/tasks/provider/TasksContentProvider.kt +++ b/app/src/main/java/org/tasks/provider/TasksContentProvider.kt @@ -38,10 +38,12 @@ class TasksContentProvider : ContentProvider() { URI_TODO_AGENDA -> { hilt.firebase.logEvent(R.string.event_todoagenda) hilt.contentProviderDao.rawQuery( - SupportSQLiteQueryBuilder - .builder(TODO_AGENDA_TABLES) - .selection(selection, selectionArgs) - .create()) + SupportSQLiteQueryBuilder + .builder(TODO_AGENDA_TABLES) + .selection(selection, selectionArgs) + .create() + .sql + ) } URI_TASKS -> hilt.contentProviderDao.getTasks() URI_LISTS -> hilt.contentProviderDao.getLists() diff --git a/data/src/commonMain/kotlin/org/tasks/data/RoomDatabaseExtensions.kt b/data/src/commonMain/kotlin/org/tasks/data/RoomDatabaseExtensions.kt new file mode 100644 index 000000000..a8d294e69 --- /dev/null +++ b/data/src/commonMain/kotlin/org/tasks/data/RoomDatabaseExtensions.kt @@ -0,0 +1,20 @@ +package org.tasks.data + +import androidx.room.RoomDatabase +import androidx.room.TransactionScope +import androidx.room.Transactor +import androidx.room.useReaderConnection +import androidx.room.useWriterConnection +import androidx.sqlite.SQLiteStatement + +suspend fun RoomDatabase.withTransaction(block: suspend TransactionScope.() -> T): T = + useWriterConnection { transactor -> + transactor.withTransaction(Transactor.SQLiteTransactionType.DEFERRED) { + block() + } + } + +suspend fun RoomDatabase.rawQuery(query: String, block: (SQLiteStatement) -> T): T = + useReaderConnection { transactor -> transactor.usePrepared(query) { block(it) } } + +suspend fun RoomDatabase.inTransaction(): Boolean = useReaderConnection { it.inTransaction() } diff --git a/data/src/commonMain/kotlin/org/tasks/data/SQLiteStatementExtensions.kt b/data/src/commonMain/kotlin/org/tasks/data/SQLiteStatementExtensions.kt new file mode 100644 index 000000000..0ed7d1f18 --- /dev/null +++ b/data/src/commonMain/kotlin/org/tasks/data/SQLiteStatementExtensions.kt @@ -0,0 +1,173 @@ +package org.tasks.data + +import androidx.room.util.getColumnIndex +import androidx.room.util.getColumnIndexOrThrow +import androidx.sqlite.SQLiteStatement +import org.tasks.data.entity.CaldavTask +import org.tasks.data.entity.Geofence +import org.tasks.data.entity.Place +import org.tasks.data.entity.Task + +/* +room kmp doesn't support raw query yet 😢 +https://issuetracker.google.com/issues/330586815 + */ +fun SQLiteStatement.getTasks(): List { + val result = mutableListOf() + val _cursorIndexOfAccountType: Int = getColumnIndex(this, "accountType") + val _cursorIndexOfParentComplete: Int = getColumnIndex(this, "parentComplete") + val _cursorIndexOfTagsString: Int = getColumnIndex(this, "tags") + val _cursorIndexOfChildren: Int = getColumnIndex(this, "children") + val _cursorIndexOfSortGroup: Int = getColumnIndex(this, "sortGroup") + val _cursorIndexOfPrimarySort: Int = getColumnIndex(this, "primarySort") + val _cursorIndexOfSecondarySort: Int = getColumnIndex(this, "secondarySort") + val _cursorIndexOfIndent: Int = getColumnIndex(this, "indent") + val _cursorIndexOfId: Int = getColumnIndexOrThrow(this, "_id") + val _cursorIndexOfTitle: Int = getColumnIndexOrThrow(this, "title") + val _cursorIndexOfPriority: Int = getColumnIndexOrThrow(this, "importance") + val _cursorIndexOfDueDate: Int = getColumnIndexOrThrow(this, "dueDate") + val _cursorIndexOfHideUntil: Int = getColumnIndexOrThrow(this, "hideUntil") + val _cursorIndexOfCreationDate: Int = getColumnIndexOrThrow(this, "created") + val _cursorIndexOfModificationDate: Int = getColumnIndexOrThrow(this, "modified") + val _cursorIndexOfCompletionDate: Int = getColumnIndexOrThrow(this, "completed") + val _cursorIndexOfDeletionDate: Int = getColumnIndexOrThrow(this, "deleted") + val _cursorIndexOfNotes: Int = getColumnIndexOrThrow(this, "notes") + val _cursorIndexOfEstimatedSeconds: Int = getColumnIndexOrThrow(this, "estimatedSeconds") + val _cursorIndexOfElapsedSeconds: Int = getColumnIndexOrThrow(this, "elapsedSeconds") + val _cursorIndexOfTimerStart: Int = getColumnIndexOrThrow(this, "timerStart") + val _cursorIndexOfRingFlags: Int = getColumnIndexOrThrow(this, "notificationFlags") + val _cursorIndexOfReminderLast: Int = getColumnIndexOrThrow(this, "lastNotified") + val _cursorIndexOfRecurrence: Int = getColumnIndexOrThrow(this, "recurrence") + val _cursorIndexOfRepeatFrom: Int = getColumnIndexOrThrow(this, "repeat_from") + val _cursorIndexOfCalendarURI: Int = getColumnIndexOrThrow(this, "calendarUri") + val _cursorIndexOfRemoteId: Int = getColumnIndexOrThrow(this, "remoteId") + val _cursorIndexOfIsCollapsed: Int = getColumnIndexOrThrow(this, "collapsed") + val _cursorIndexOfParent: Int = getColumnIndexOrThrow(this, "parent") + val _cursorIndexOfOrder: Int = getColumnIndexOrThrow(this, "order") + val _cursorIndexOfReadOnly: Int = getColumnIndexOrThrow(this, "read_only") + val _cursorIndexOfId_1: Int = getColumnIndex(this, "cd_id") + val _cursorIndexOfTask: Int = getColumnIndex(this, "cd_task") + val _cursorIndexOfCalendar: Int = getColumnIndex(this, "cd_calendar") + val _cursorIndexOfRemoteId_1: Int = getColumnIndex(this, "cd_remote_id") + val _cursorIndexOfObj: Int = getColumnIndex(this, "cd_object") + val _cursorIndexOfEtag: Int = getColumnIndex(this, "cd_etag") + val _cursorIndexOfLastSync: Int = getColumnIndex(this, "cd_last_sync") + val _cursorIndexOfDeleted: Int = getColumnIndex(this, "cd_deleted") + val _cursorIndexOfRemoteParent: Int = getColumnIndex(this, "cd_remote_parent") + val _cursorIndexOfIsMoved: Int = getColumnIndex(this, "gt_moved") + val _cursorIndexOfRemoteOrder: Int = getColumnIndex(this, "gt_remote_order") + val _cursorIndexOfId_2: Int = getColumnIndex(this, "geofence_id") + val _cursorIndexOfTask_1: Int = getColumnIndex(this, "task") + val _cursorIndexOfPlace: Int = getColumnIndex(this, "place") + val _cursorIndexOfIsArrival: Int = getColumnIndex(this, "arrival") + val _cursorIndexOfIsDeparture: Int = getColumnIndex(this, "departure") + val _cursorIndexOfId_3: Int = getColumnIndex(this, "place_id") + val _cursorIndexOfUid: Int = getColumnIndex(this, "uid") + val _cursorIndexOfName: Int = getColumnIndex(this, "name") + val _cursorIndexOfAddress: Int = getColumnIndex(this, "address") + val _cursorIndexOfPhone: Int = getColumnIndex(this, "phone") + val _cursorIndexOfUrl: Int = getColumnIndex(this, "url") + val _cursorIndexOfLatitude: Int = getColumnIndex(this, "latitude") + val _cursorIndexOfLongitude: Int = getColumnIndex(this, "longitude") + val _cursorIndexOfColor: Int = getColumnIndex(this, "place_color") + val _cursorIndexOfIcon: Int = getColumnIndex(this, "place_icon") + val _cursorIndexOfOrder_1: Int = getColumnIndex(this, "place_order") + val _cursorIndexOfRadius: Int = getColumnIndex(this, "radius") + while (step()) { + val task = Task( + id = getLong(_cursorIndexOfId), + title = getTextOrNull(_cursorIndexOfTitle), + priority = getInt(_cursorIndexOfPriority), + dueDate = getLong(_cursorIndexOfDueDate), + hideUntil = getLong(_cursorIndexOfHideUntil), + creationDate = getLong(_cursorIndexOfCreationDate), + modificationDate = getLong(_cursorIndexOfModificationDate), + completionDate = getLong(_cursorIndexOfCompletionDate), + deletionDate = getLong(_cursorIndexOfDeletionDate), + notes = getTextOrNull(_cursorIndexOfNotes), + estimatedSeconds = getInt(_cursorIndexOfEstimatedSeconds), + elapsedSeconds = getInt(_cursorIndexOfElapsedSeconds), + timerStart = getLong(_cursorIndexOfTimerStart), + ringFlags = getInt(_cursorIndexOfRingFlags), + reminderLast = getLong(_cursorIndexOfReminderLast), + recurrence = getTextOrNull(_cursorIndexOfRecurrence), + repeatFrom = getInt(_cursorIndexOfRepeatFrom), + calendarURI = getTextOrNull(_cursorIndexOfCalendarURI), + remoteId = getTextOrNull(_cursorIndexOfRemoteId), + isCollapsed = getBoolean(_cursorIndexOfIsCollapsed), + parent = getLong(_cursorIndexOfParent), + order = getLongOrNull(_cursorIndexOfOrder), + readOnly = getBoolean(_cursorIndexOfReadOnly), + ) + val caldavTask = getLongOrNull(_cursorIndexOfId_1)?.takeIf { it > 0 }?.let { + CaldavTask( + id = it, + task = getLong(_cursorIndexOfTask), + calendar = getTextOrNull(_cursorIndexOfCalendar), + remoteId = getTextOrNull(_cursorIndexOfRemoteId_1), + obj = getTextOrNull(_cursorIndexOfObj), + etag = getTextOrNull(_cursorIndexOfEtag), + lastSync = getLong(_cursorIndexOfLastSync), + deleted = getLong(_cursorIndexOfDeleted), + remoteParent = getTextOrNull(_cursorIndexOfRemoteParent), + isMoved = getBoolean(_cursorIndexOfIsMoved), + remoteOrder = getLong(_cursorIndexOfRemoteOrder), + ) + } + val accountType = getInt(_cursorIndexOfAccountType) + val geofence = getLongOrNull(_cursorIndexOfId_2)?.takeIf { it > 0 }?.let { + Geofence( + id = it, + task = getLong(_cursorIndexOfTask_1), + place = getTextOrNull(_cursorIndexOfPlace), + isArrival = getBoolean(_cursorIndexOfIsArrival), + isDeparture = getBoolean(_cursorIndexOfIsDeparture), + ) + } + val place = getLongOrNull(_cursorIndexOfId_3)?.takeIf { it > 0 }?.let { + Place( + id = it, + uid = getTextOrNull(_cursorIndexOfUid), + name = getTextOrNull(_cursorIndexOfName), + address = getTextOrNull(_cursorIndexOfAddress), + phone = getTextOrNull(_cursorIndexOfPhone), + url = getTextOrNull(_cursorIndexOfUrl), + latitude = getDouble(_cursorIndexOfLatitude), + longitude = getDouble(_cursorIndexOfLongitude), + color = getInt(_cursorIndexOfColor), + icon = getInt(_cursorIndexOfIcon), + order = getInt(_cursorIndexOfOrder_1), + radius = getInt(_cursorIndexOfRadius), + ) + } + result.add( + TaskContainer( + task = task, + caldavTask = caldavTask, + accountType = accountType, + location = if (geofence != null && place != null) { + Location(geofence, place) + } else { + null + }, + tagsString = getTextOrNull(_cursorIndexOfTagsString), + indent = getIntOrNull(_cursorIndexOfIndent) ?: 0, + sortGroup = getLongOrNull(_cursorIndexOfSortGroup), + children = getIntOrNull(_cursorIndexOfChildren) ?: 0, + primarySort = getLongOrNull(_cursorIndexOfPrimarySort) ?: 0, + secondarySort = getLongOrNull(_cursorIndexOfSecondarySort) ?: 0, + parentComplete = getBoolean(_cursorIndexOfParentComplete), + ) + ) + } + return result +} + +private fun SQLiteStatement.getTextOrNull(index: Int): String? = + if (index == -1 || isNull(index)) null else this.getText(index) + +private fun SQLiteStatement.getLongOrNull(index: Int): Long? = + if (index == -1 || isNull(index)) null else this.getLong(index) + +private fun SQLiteStatement.getIntOrNull(index: Int): Int? = + if (index == -1 || isNull(index)) null else this.getInt(index) diff --git a/data/src/commonMain/kotlin/org/tasks/data/dao/ContentProviderDao.kt b/data/src/commonMain/kotlin/org/tasks/data/dao/Astrid2ContentProviderDao.kt similarity index 68% rename from data/src/commonMain/kotlin/org/tasks/data/dao/ContentProviderDao.kt rename to data/src/commonMain/kotlin/org/tasks/data/dao/Astrid2ContentProviderDao.kt index ad40eb771..0628f3470 100644 --- a/data/src/commonMain/kotlin/org/tasks/data/dao/ContentProviderDao.kt +++ b/data/src/commonMain/kotlin/org/tasks/data/dao/Astrid2ContentProviderDao.kt @@ -1,15 +1,12 @@ package org.tasks.data.dao -import android.database.Cursor import androidx.room.Dao import androidx.room.Query -import androidx.room.RawQuery -import androidx.sqlite.db.SupportSQLiteQuery import org.tasks.data.entity.TagData import org.tasks.data.entity.Task @Dao -interface ContentProviderDao { +interface Astrid2ContentProviderDao { @Query("SELECT name FROM tags WHERE task = :taskId ORDER BY UPPER(name) ASC") suspend fun getTagNames(taskId: Long): List @@ -30,16 +27,4 @@ interface ContentProviderDao { @Query("SELECT * FROM tagdata WHERE name IS NOT NULL AND name != '' ORDER BY UPPER(name) ASC") suspend fun tagDataOrderedByName(): List - - @Query("SELECT * FROM tasks") - fun getTasks(): Cursor - - @Query(""" - SELECT caldav_lists.*, caldav_accounts.cda_name - FROM caldav_lists - INNER JOIN caldav_accounts ON cdl_account = cda_uuid""") - fun getLists(): Cursor - - @RawQuery - fun rawQuery(query: SupportSQLiteQuery): Cursor -} \ No newline at end of file +} 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 e9b545da2..3736d5c12 100644 --- a/data/src/commonMain/kotlin/org/tasks/data/dao/TaskDao.kt +++ b/data/src/commonMain/kotlin/org/tasks/data/dao/TaskDao.kt @@ -3,10 +3,8 @@ package org.tasks.data.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.Query -import androidx.room.RawQuery import androidx.room.Update -import androidx.room.withTransaction -import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.room.execSQL import co.touchlab.kermit.Logger import org.tasks.data.BuildConfig import org.tasks.data.TaskContainer @@ -16,8 +14,11 @@ import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.data.db.SuspendDbUtils.eachChunk import org.tasks.data.entity.Alarm import org.tasks.data.entity.Task +import org.tasks.data.getTasks +import org.tasks.data.rawQuery 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 @@ -109,28 +110,25 @@ FROM ( abstract suspend fun clearCompletedCalendarEvents(): Int open suspend fun fetchTasks(callback: suspend () -> List): List = - database.withTransaction { - val start = if (BuildConfig.DEBUG) DateTimeUtils2.currentTimeMillis() else 0 - val queries = callback() - val last = queries.size - 1 - for (i in 0 until last) { - query(SimpleSQLiteQuery(queries[i])) - } - val result = fetchTasks(SimpleSQLiteQuery(queries[last])) - Logger.v("TaskDao") { - "${DateTimeUtils2.currentTimeMillis() - start}ms: ${queries.joinToString(";\n")}" - } - result + database.withTransaction { + val start = if (BuildConfig.DEBUG) DateTimeUtils2.currentTimeMillis() else 0 + val queries = callback() + val last = queries.size - 1 + for (i in 0 until last) { + execSQL(queries[i]) } + val result = usePrepared(queries[last]) { it.getTasks() } + Logger.v("TaskDao") { + "${DateTimeUtils2.currentTimeMillis() - start}ms: ${queries.joinToString(";\n")}" + } + result + } - @RawQuery - internal abstract suspend fun query(query: SimpleSQLiteQuery): Int - - @RawQuery - abstract suspend fun fetchTasks(query: SimpleSQLiteQuery): List + suspend fun fetchTasks(query: String): List = + database.rawQuery(query) { it.getTasks() } - @RawQuery - abstract suspend fun countRaw(query: SimpleSQLiteQuery): Int + suspend fun countRaw(query: String): Int = + database.rawQuery(query) { if (it.step()) it.getInt(0) else 0 } suspend fun touch(ids: List, now: Long = DateTimeUtils2.currentTimeMillis()) = ids.eachChunk { internalTouch(it, now) } diff --git a/data/src/commonMain/kotlin/org/tasks/data/db/Database.kt b/data/src/commonMain/kotlin/org/tasks/data/db/Database.kt index dc1dd03ed..3cab8ddc9 100644 --- a/data/src/commonMain/kotlin/org/tasks/data/db/Database.kt +++ b/data/src/commonMain/kotlin/org/tasks/data/db/Database.kt @@ -11,7 +11,7 @@ import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavCalendar import org.tasks.data.dao.CaldavDao import org.tasks.data.entity.CaldavTask -import org.tasks.data.dao.ContentProviderDao +import org.tasks.data.dao.Astrid2ContentProviderDao import org.tasks.data.dao.DeletionDao import org.tasks.data.entity.Filter import org.tasks.data.dao.FilterDao @@ -78,7 +78,7 @@ abstract class Database : RoomDatabase() { abstract fun taskDao(): TaskDao abstract fun caldavDao(): CaldavDao abstract fun deletionDao(): DeletionDao - abstract fun contentProviderDao(): ContentProviderDao + abstract fun contentProviderDao(): Astrid2ContentProviderDao abstract fun upgraderDao(): UpgraderDao abstract fun principalDao(): PrincipalDao diff --git a/deps_fdroid.txt b/deps_fdroid.txt index fd851875a..4644c5e16 100644 --- a/deps_fdroid.txt +++ b/deps_fdroid.txt @@ -234,13 +234,15 @@ +| | | \--- androidx.sqlite:sqlite-android:2.5.0-alpha02 +| | | +--- androidx.annotation:annotation:1.8.0 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) -+| | | \--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 (c) ++| | | +--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 (c) ++| | | \--- androidx.sqlite:sqlite-bundled:2.5.0-alpha02 (c) +| | +--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 +| | | \--- androidx.sqlite:sqlite-framework-android:2.5.0-alpha02 +| | | +--- androidx.annotation:annotation:1.8.0 (*) +| | | +--- androidx.sqlite:sqlite:2.5.0-alpha02 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) -+| | | \--- androidx.sqlite:sqlite:2.5.0-alpha02 (c) ++| | | +--- androidx.sqlite:sqlite:2.5.0-alpha02 (c) ++| | | \--- androidx.sqlite:sqlite-bundled:2.5.0-alpha02 (c) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) +| | +--- org.jetbrains.kotlinx:atomicfu:0.17.0 +| | | \--- org.jetbrains.kotlinx:atomicfu-jvm:0.17.0 @@ -603,6 +605,12 @@ ++--- androidx.lifecycle:lifecycle-runtime-ktx:2.8.0 (*) ++--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0 (*) ++--- androidx.room:room-runtime:2.7.0-alpha02 (*) +++--- androidx.sqlite:sqlite-bundled:2.5.0-alpha02 ++| \--- androidx.sqlite:sqlite-bundled-android:2.5.0-alpha02 ++| +--- androidx.sqlite:sqlite:2.5.0-alpha02 (*) ++| +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) ++| +--- androidx.sqlite:sqlite:2.5.0-alpha02 (c) ++| \--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 (c) ++--- androidx.appcompat:appcompat:1.6.1 (*) ++--- io.noties.markwon:core:4.6.2 +| +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*) diff --git a/deps_googleplay.txt b/deps_googleplay.txt index 255ee6d2b..e1e142ace 100644 --- a/deps_googleplay.txt +++ b/deps_googleplay.txt @@ -594,13 +594,15 @@ +| | | \--- androidx.sqlite:sqlite-android:2.5.0-alpha02 +| | | +--- androidx.annotation:annotation:1.8.0 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) -+| | | \--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 (c) ++| | | +--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 (c) ++| | | \--- androidx.sqlite:sqlite-bundled:2.5.0-alpha02 (c) +| | +--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 +| | | \--- androidx.sqlite:sqlite-framework-android:2.5.0-alpha02 +| | | +--- androidx.annotation:annotation:1.8.0 (*) +| | | +--- androidx.sqlite:sqlite:2.5.0-alpha02 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) -+| | | \--- androidx.sqlite:sqlite:2.5.0-alpha02 (c) ++| | | +--- androidx.sqlite:sqlite:2.5.0-alpha02 (c) ++| | | \--- androidx.sqlite:sqlite-bundled:2.5.0-alpha02 (c) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) +| | +--- org.jetbrains.kotlinx:atomicfu:0.17.0 +| | | \--- org.jetbrains.kotlinx:atomicfu-jvm:0.17.0 @@ -832,6 +834,12 @@ ++--- androidx.lifecycle:lifecycle-runtime-ktx:2.8.0 (*) ++--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0 (*) ++--- androidx.room:room-runtime:2.7.0-alpha02 (*) +++--- androidx.sqlite:sqlite-bundled:2.5.0-alpha02 ++| \--- androidx.sqlite:sqlite-bundled-android:2.5.0-alpha02 ++| +--- androidx.sqlite:sqlite:2.5.0-alpha02 (*) ++| +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) ++| +--- androidx.sqlite:sqlite:2.5.0-alpha02 (c) ++| \--- androidx.sqlite:sqlite-framework:2.5.0-alpha02 (c) ++--- androidx.appcompat:appcompat:1.6.1 (*) ++--- io.noties.markwon:core:4.6.2 +| +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6bf31603..bd52843c0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -83,6 +83,7 @@ androidx-preference = { module = "androidx.preference:preference", version.ref = androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } androidx-room = { module = "androidx.room:room-runtime", version.ref = "room" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room"} +androidx-sqlite = { module = "androidx.sqlite:sqlite-bundled", version = "2.5.0-alpha02" } androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" } androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" } androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test" }