diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 94074c0ed2..7ca8545cd2 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -155,6 +155,7 @@ object Dependencies { const val compiler = "androidx.room:room-compiler:${Versions.Androidx.room}" const val ktx = "androidx.room:room-ktx:${Versions.Androidx.room}" const val runtime = "androidx.room:room-runtime:${Versions.Androidx.room}" + const val testing = "androidx.room:room-testing:${Versions.Androidx.room}" } object Mlkit { diff --git a/buildSrc/src/main/kotlin/RoomSchemaArgProvider.kt b/buildSrc/src/main/kotlin/RoomSchemaArgProvider.kt new file mode 100644 index 0000000000..17a09a0c57 --- /dev/null +++ b/buildSrc/src/main/kotlin/RoomSchemaArgProvider.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.File +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.process.CommandLineArgumentProvider + +// https://developer.android.com/training/data-storage/room/migrating-db-versions#test +class RoomSchemaArgProvider( + @get:InputDirectory @get:PathSensitive(PathSensitivity.RELATIVE) val schemaDir: File +) : CommandLineArgumentProvider { + + override fun asArguments() = listOf("-Aroom.schemaLocation=${schemaDir.path}") +} diff --git a/engine/build.gradle.kts b/engine/build.gradle.kts index bcde40f76f..553ff1c10b 100644 --- a/engine/build.gradle.kts +++ b/engine/build.gradle.kts @@ -37,10 +37,19 @@ android { testInstrumentationRunner = Dependencies.androidJunitRunner // need to specify this to prevent junit runner from going deep into our dependencies testInstrumentationRunnerArguments["package"] = "com.google.android.fhir" + + javaCompileOptions { + annotationProcessorOptions { + compilerArgumentProviders(RoomSchemaArgProvider(File(projectDir, "schemas"))) + } + } } sourceSets { - getByName("androidTest").apply { resources.setSrcDirs(listOf("test-data")) } + getByName("androidTest").apply { + resources.setSrcDirs(listOf("test-data")) + assets.srcDirs("$projectDir/schemas") + } getByName("test").apply { resources.setSrcDirs(listOf("test-data")) } } @@ -88,6 +97,7 @@ dependencies { androidTestImplementation(Dependencies.AndroidxTest.extJunitKtx) androidTestImplementation(Dependencies.AndroidxTest.runner) androidTestImplementation(Dependencies.AndroidxTest.workTestingRuntimeKtx) + androidTestImplementation(Dependencies.Room.testing) androidTestImplementation(Dependencies.junit) androidTestImplementation(Dependencies.truth) diff --git a/engine/schemas/com.google.android.fhir.db.impl.ResourceDatabase/1.json b/engine/schemas/com.google.android.fhir.db.impl.ResourceDatabase/1.json new file mode 100644 index 0000000000..587358f16a --- /dev/null +++ b/engine/schemas/com.google.android.fhir.db.impl.ResourceDatabase/1.json @@ -0,0 +1,958 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "e9134c73c4d82cebd7084432c2b25f1a", + "entities": [ + { + "tableName": "ResourceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `resourceId` TEXT NOT NULL, `serializedResource` TEXT NOT NULL, `versionId` TEXT, `lastUpdatedRemote` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resourceId", + "columnName": "resourceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serializedResource", + "columnName": "serializedResource", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastUpdatedRemote", + "columnName": "lastUpdatedRemote", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_ResourceEntity_resourceUuid", + "unique": true, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ResourceEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + }, + { + "name": "index_ResourceEntity_resourceType_resourceId", + "unique": true, + "columnNames": [ + "resourceType", + "resourceId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ResourceEntity_resourceType_resourceId` ON `${TABLE_NAME}` (`resourceType`, `resourceId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "StringIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_StringIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StringIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_StringIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StringIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "ReferenceIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_ReferenceIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ReferenceIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_ReferenceIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ReferenceIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "TokenIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_system` TEXT, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.system", + "columnName": "index_system", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_TokenIndexEntity_resourceType_index_name_index_system_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_system", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TokenIndexEntity_resourceType_index_name_index_system_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_system`, `index_value`)" + }, + { + "name": "index_TokenIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TokenIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "QuantityIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_system` TEXT NOT NULL, `index_code` TEXT NOT NULL, `index_value` REAL NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.system", + "columnName": "index_system", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.code", + "columnName": "index_code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_QuantityIndexEntity_resourceType_index_name_index_value_index_code", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value", + "index_code" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_QuantityIndexEntity_resourceType_index_name_index_value_index_code` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`, `index_code`)" + }, + { + "name": "index_QuantityIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_QuantityIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "UriIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_UriIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_UriIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_UriIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_UriIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "DateIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_from` INTEGER NOT NULL, `index_to` INTEGER NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.from", + "columnName": "index_from", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "index.to", + "columnName": "index_to", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_DateIndexEntity_resourceType_index_name_index_from_index_to", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_from", + "index_to" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateIndexEntity_resourceType_index_name_index_from_index_to` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_from`, `index_to`)" + }, + { + "name": "index_DateIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "DateTimeIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_from` INTEGER NOT NULL, `index_to` INTEGER NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.from", + "columnName": "index_from", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "index.to", + "columnName": "index_to", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_DateTimeIndexEntity_resourceType_index_name_index_from_index_to", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_from", + "index_to" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateTimeIndexEntity_resourceType_index_name_index_from_index_to` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_from`, `index_to`)" + }, + { + "name": "index_DateTimeIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateTimeIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "NumberIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` REAL NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_NumberIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_NumberIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_NumberIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_NumberIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "SyncedResourceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`resourceType` TEXT NOT NULL, `lastUpdate` TEXT NOT NULL, PRIMARY KEY(`resourceType`))", + "fields": [ + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdate", + "columnName": "lastUpdate", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "resourceType" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalChangeEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceType` TEXT NOT NULL, `resourceId` TEXT NOT NULL, `timestamp` TEXT NOT NULL, `type` INTEGER NOT NULL, `payload` TEXT NOT NULL, `versionId` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resourceId", + "columnName": "resourceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "payload", + "columnName": "payload", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_LocalChangeEntity_resourceType_resourceId", + "unique": false, + "columnNames": [ + "resourceType", + "resourceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_LocalChangeEntity_resourceType_resourceId` ON `${TABLE_NAME}` (`resourceType`, `resourceId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "PositionIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_latitude` REAL NOT NULL, `index_longitude` REAL NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.latitude", + "columnName": "index_latitude", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "index.longitude", + "columnName": "index_longitude", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_PositionIndexEntity_resourceType_index_latitude_index_longitude", + "unique": false, + "columnNames": [ + "resourceType", + "index_latitude", + "index_longitude" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PositionIndexEntity_resourceType_index_latitude_index_longitude` ON `${TABLE_NAME}` (`resourceType`, `index_latitude`, `index_longitude`)" + }, + { + "name": "index_PositionIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PositionIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e9134c73c4d82cebd7084432c2b25f1a')" + ] + } +} \ No newline at end of file diff --git a/engine/schemas/com.google.android.fhir.db.impl.ResourceDatabase/2.json b/engine/schemas/com.google.android.fhir.db.impl.ResourceDatabase/2.json new file mode 100644 index 0000000000..c3f1cb5f10 --- /dev/null +++ b/engine/schemas/com.google.android.fhir.db.impl.ResourceDatabase/2.json @@ -0,0 +1,932 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "ff9a029a72466b4877b2f7654d8c0095", + "entities": [ + { + "tableName": "ResourceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `resourceId` TEXT NOT NULL, `serializedResource` TEXT NOT NULL, `versionId` TEXT, `lastUpdatedRemote` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resourceId", + "columnName": "resourceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serializedResource", + "columnName": "serializedResource", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastUpdatedRemote", + "columnName": "lastUpdatedRemote", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_ResourceEntity_resourceUuid", + "unique": true, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ResourceEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + }, + { + "name": "index_ResourceEntity_resourceType_resourceId", + "unique": true, + "columnNames": [ + "resourceType", + "resourceId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ResourceEntity_resourceType_resourceId` ON `${TABLE_NAME}` (`resourceType`, `resourceId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "StringIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_StringIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StringIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_StringIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StringIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "ReferenceIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_ReferenceIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ReferenceIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_ReferenceIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ReferenceIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "TokenIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_system` TEXT, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.system", + "columnName": "index_system", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_TokenIndexEntity_resourceType_index_name_index_system_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_system", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TokenIndexEntity_resourceType_index_name_index_system_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_system`, `index_value`)" + }, + { + "name": "index_TokenIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TokenIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "QuantityIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_system` TEXT NOT NULL, `index_code` TEXT NOT NULL, `index_value` REAL NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.system", + "columnName": "index_system", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.code", + "columnName": "index_code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_QuantityIndexEntity_resourceType_index_name_index_value_index_code", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value", + "index_code" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_QuantityIndexEntity_resourceType_index_name_index_value_index_code` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`, `index_code`)" + }, + { + "name": "index_QuantityIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_QuantityIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "UriIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` TEXT NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_UriIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_UriIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_UriIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_UriIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "DateIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_from` INTEGER NOT NULL, `index_to` INTEGER NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.from", + "columnName": "index_from", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "index.to", + "columnName": "index_to", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_DateIndexEntity_resourceType_index_name_index_from_index_to", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_from", + "index_to" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateIndexEntity_resourceType_index_name_index_from_index_to` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_from`, `index_to`)" + }, + { + "name": "index_DateIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "DateTimeIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_from` INTEGER NOT NULL, `index_to` INTEGER NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.from", + "columnName": "index_from", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "index.to", + "columnName": "index_to", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_DateTimeIndexEntity_resourceType_index_name_index_from_index_to", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_from", + "index_to" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateTimeIndexEntity_resourceType_index_name_index_from_index_to` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_from`, `index_to`)" + }, + { + "name": "index_DateTimeIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_DateTimeIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "NumberIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_name` TEXT NOT NULL, `index_path` TEXT NOT NULL, `index_value` REAL NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.name", + "columnName": "index_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.path", + "columnName": "index_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.value", + "columnName": "index_value", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_NumberIndexEntity_resourceType_index_name_index_value", + "unique": false, + "columnNames": [ + "resourceType", + "index_name", + "index_value" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_NumberIndexEntity_resourceType_index_name_index_value` ON `${TABLE_NAME}` (`resourceType`, `index_name`, `index_value`)" + }, + { + "name": "index_NumberIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_NumberIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + }, + { + "tableName": "LocalChangeEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceType` TEXT NOT NULL, `resourceId` TEXT NOT NULL, `timestamp` TEXT NOT NULL, `type` INTEGER NOT NULL, `payload` TEXT NOT NULL, `versionId` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resourceId", + "columnName": "resourceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "payload", + "columnName": "payload", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionId", + "columnName": "versionId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_LocalChangeEntity_resourceType_resourceId", + "unique": false, + "columnNames": [ + "resourceType", + "resourceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_LocalChangeEntity_resourceType_resourceId` ON `${TABLE_NAME}` (`resourceType`, `resourceId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "PositionIndexEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resourceUuid` BLOB NOT NULL, `resourceType` TEXT NOT NULL, `index_latitude` REAL NOT NULL, `index_longitude` REAL NOT NULL, FOREIGN KEY(`resourceUuid`) REFERENCES `ResourceEntity`(`resourceUuid`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resourceUuid", + "columnName": "resourceUuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index.latitude", + "columnName": "index_latitude", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "index.longitude", + "columnName": "index_longitude", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_PositionIndexEntity_resourceType_index_latitude_index_longitude", + "unique": false, + "columnNames": [ + "resourceType", + "index_latitude", + "index_longitude" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PositionIndexEntity_resourceType_index_latitude_index_longitude` ON `${TABLE_NAME}` (`resourceType`, `index_latitude`, `index_longitude`)" + }, + { + "name": "index_PositionIndexEntity_resourceUuid", + "unique": false, + "columnNames": [ + "resourceUuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PositionIndexEntity_resourceUuid` ON `${TABLE_NAME}` (`resourceUuid`)" + } + ], + "foreignKeys": [ + { + "table": "ResourceEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "resourceUuid" + ], + "referencedColumns": [ + "resourceUuid" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff9a029a72466b4877b2f7654d8c0095')" + ] + } +} \ No newline at end of file diff --git a/engine/src/androidTest/java/com/google/android/fhir/db/impl/ResourceDatabaseMigrationTest.kt b/engine/src/androidTest/java/com/google/android/fhir/db/impl/ResourceDatabaseMigrationTest.kt new file mode 100644 index 0000000000..c91e6db7af --- /dev/null +++ b/engine/src/androidTest/java/com/google/android/fhir/db/impl/ResourceDatabaseMigrationTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.db.impl + +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import ca.uhn.fhir.context.FhirContext +import com.google.common.truth.Truth.assertThat +import java.io.IOException +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.HumanName +import org.hl7.fhir.r4.model.Patient +import org.hl7.fhir.r4.model.ResourceType +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ResourceDatabaseMigrationTest { + + @get:Rule + val helper: MigrationTestHelper = + MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), ResourceDatabase::class.java) + + @Test + @Throws(IOException::class) + fun migrate1To2_should_not_throw_exception(): Unit = runBlocking { + val insertedPatientJson: String = + Patient() + .apply { + id = "migrate1-2-test" + addName( + HumanName().apply { + addGiven("Jane") + family = "Doe" + } + ) + } + .let { FhirContext.forR4Cached().newJsonParser().encodeResourceToString(it) } + + helper.createDatabase(DB_NAME, 1).apply { + execSQL( + "INSERT INTO ResourceEntity (resourceUuid, resourceType, resourceId, serializedResource) VALUES ('migrate1-2-test', 'Patient', 'migrate1-2-test', '$insertedPatientJson' );" + ) + close() + } + + val readPatientJson: String? + // Open latest version of the database. Room will validate the schema + // once all migrations execute. + Room.databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + ResourceDatabase::class.java, + DB_NAME + ) + .addMigrations(MIGRATION_1_2) + .build() + .apply { + readPatientJson = this.resourceDao().getResource("migrate1-2-test", ResourceType.Patient) + openHelper.writableDatabase.close() + } + + assertThat(readPatientJson).isEqualTo(insertedPatientJson) + } + + companion object { + const val DB_NAME = "migration_tests.db" + } +} diff --git a/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt b/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt index 32258fbc55..c606513ee6 100644 --- a/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt +++ b/engine/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt @@ -92,6 +92,8 @@ internal class DatabaseImpl( ) { DatabaseEncryptionKeyProvider.getOrCreatePassphrase(DATABASE_PASSPHRASE_NAME) } } } + + addMigrations(MIGRATION_1_2) } .build() } diff --git a/engine/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt b/engine/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt index bc5e84d02f..d3dadd3d10 100644 --- a/engine/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt +++ b/engine/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt @@ -19,6 +19,8 @@ package com.google.android.fhir.db.impl import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.google.android.fhir.db.impl.dao.LocalChangeDao import com.google.android.fhir.db.impl.dao.ResourceDao import com.google.android.fhir.db.impl.entities.DateIndexEntity @@ -48,11 +50,18 @@ import com.google.android.fhir.db.impl.entities.UriIndexEntity LocalChangeEntity::class, PositionIndexEntity::class ], - version = 1, - exportSchema = false + version = 2, + exportSchema = true ) @TypeConverters(DbTypeConverters::class) internal abstract class ResourceDatabase : RoomDatabase() { abstract fun resourceDao(): ResourceDao abstract fun localChangeDao(): LocalChangeDao } + +val MIGRATION_1_2 = + object : Migration(/* startVersion = */ 1, /* endVersion = */ 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("DROP table if exists SyncedResourceEntity") + } + } diff --git a/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt b/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt index da3eb78927..15048a0fed 100644 --- a/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt +++ b/engine/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt @@ -16,6 +16,7 @@ package com.google.android.fhir.db.impl.dao +import androidx.annotation.VisibleForTesting import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy @@ -45,6 +46,7 @@ import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType @Dao +@VisibleForTesting internal abstract class ResourceDao { // this is ugly but there is no way to inject these right now in Room as it is the one creating // the dao