SQLiteOpenHelperWriter.kt revision fa3905934508aa143d899cb9b62b3b074748c9e9
1d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar/*
2d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * Copyright (C) 2016 The Android Open Source Project
3d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar *
4d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
5d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * you may not use this file except in compliance with the License.
6d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * You may obtain a copy of the License at
7d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar *
8d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
9d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar *
10d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * Unless required by applicable law or agreed to in writing, software
11d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
12d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * See the License for the specific language governing permissions and
14d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * limitations under the License.
15d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar */
16d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar
17d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarpackage com.android.support.room.writer
18d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar
19c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyarimport android.support.annotation.VisibleForTesting
20fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyarimport com.android.support.room.ext.AndroidTypeNames
21d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.android.support.room.ext.L
22d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.android.support.room.ext.N
23fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyarimport com.android.support.room.ext.RoomTypeNames
24c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyarimport com.android.support.room.ext.S
25d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.android.support.room.ext.SupportDbTypeNames
26d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.android.support.room.ext.T
27fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyarimport com.android.support.room.ext.typeName
28d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.android.support.room.solver.CodeGenScope
29d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.android.support.room.vo.Database
30c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyarimport com.android.support.room.vo.Entity
31d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.squareup.javapoet.MethodSpec
32d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.squareup.javapoet.ParameterSpec
33d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.squareup.javapoet.TypeName
34d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport com.squareup.javapoet.TypeSpec
35d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarimport javax.lang.model.element.Modifier.PUBLIC
36d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar
37d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar/**
38d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar * Create an open helper using SupportSQLiteOpenHelperFactory
39d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar */
40d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyarclass SQLiteOpenHelperWriter(val database : Database) {
41fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    companion object {
42fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        // must match the runtime property Room#MASTER_TABLE_NAME
43fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        val MASTER_TABLE_NAME = "room_master_table"
44fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        val MASTER_TABLE_ID_COLUMN = "id"
45fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        val MASTER_TABLE_IDENTITY_HASH_COLUMN = "identity_hash"
46fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        val MASTER_TABLE_ID = 42
47fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    }
48d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    fun write(outVar : String, configuration : ParameterSpec, scope: CodeGenScope) {
49d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        scope.builder().apply {
50d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            val sqliteConfigVar = scope.getTmpVar("_sqliteConfig")
51d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            val callbackVar = scope.getTmpVar("_openCallback")
52d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addStatement("final $T $L = $L",
53d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CALLBACK,
54d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    callbackVar, createOpenCallback())
55d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            // build configuration
56d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addStatement(
57d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    """
58d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    final $T $L = $T.builder($N.context)
59d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    .name($N.name)
60d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    .version($N.version)
61d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    .callback($L)
62d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    .build()
63d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    """.trimIndent(),
64d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG, sqliteConfigVar,
65d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG,
66d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    configuration, configuration, configuration, callbackVar)
67d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addStatement("final $T $N = $N.sqliteOpenHelperFactory.create($L)",
68d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    SupportDbTypeNames.SQLITE_OPEN_HELPER, outVar,
69d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar                    configuration, sqliteConfigVar)
70d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        }
71d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    }
72d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar
73d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    private fun createOpenCallback() : TypeSpec {
74d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        return TypeSpec.anonymousClassBuilder("").apply {
75d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            superclass(SupportDbTypeNames.SQLITE_OPEN_HELPER_CALLBACK)
76d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addMethod(createOnCreate())
77d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addMethod(createOnUpgrade())
78d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addMethod(createOnDowngrade())
7934e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar            addMethod(createOnOpen())
8034e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar        }.build()
8134e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar    }
8234e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar
8334e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar    private fun createOnOpen(): MethodSpec {
8434e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar        return MethodSpec.methodBuilder("onOpen").apply {
8534e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar            addModifiers(PUBLIC)
8634e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar            addParameter(SupportDbTypeNames.DB, "_db")
87fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            addStatement("String identityHash = \"\"")
88fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            addStatement("$T cursor = _db.rawQuery($S, $T.EMPTY_STRING_ARRAY)",
89fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                    AndroidTypeNames.CURSOR, readIdentityHashQuery(),
90fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                    RoomTypeNames.STRING_UTIL)
91fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            beginControlFlow("try").apply {
92fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                beginControlFlow("if (cursor.moveToFirst())").apply {
93fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                    addStatement("identityHash = cursor.getString(0)")
94fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                }
95fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                endControlFlow()
96fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            }
97fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            nextControlFlow("finally").apply {
98fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                addStatement("cursor.close()")
99fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            }
100fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            endControlFlow()
101fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            beginControlFlow("if(!$S.equals(identityHash))", database.identityHash).apply {
102fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                addStatement("throw new $T($S)", IllegalStateException::class.typeName(),
103fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                        "Room cannot verify the data integrity. Looks like you've changed schema" +
104fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                                " but forgot to update the version number. You can simply fix" +
105fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                                " this by increasing the version number.")
106fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            }
107fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            endControlFlow()
108fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar            addStatement("mDatabase = _db")
10934e5031083f735db3a395b0f6aa430880b072d71Yigit Boyar            addStatement("internalInitInvalidationTracker(_db)")
110d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        }.build()
111d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    }
112d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar
113dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar    private fun MethodSpec.Builder.writeCreateStatements() {
114fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        addStatement("_db.execSQL($S)", createMasterTableQuery())
115fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        addStatement("_db.execSQL($S)", setIdentityHashQuery())
116dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar        // this is already called in transaction so no need for a transaction
117dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar        database.entities.forEach {
118dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar            addStatement("_db.execSQL($S)", createQuery(it))
119dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar        }
120dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar        database.entities.forEach {
121dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar            it.createIndexQueries.forEach {
122dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar                addStatement("_db.execSQL($S)", it)
123dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar            }
124dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar        }
125dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar    }
126dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar
127fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    private fun setIdentityHashQuery(): String {
128fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        return "INSERT OR REPLACE INTO $MASTER_TABLE_NAME VALUES($MASTER_TABLE_ID," +
129fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                "\"${database.identityHash}\")"
130fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    }
131fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar
132fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    private fun readIdentityHashQuery() : String {
133fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        return "SELECT $MASTER_TABLE_IDENTITY_HASH_COLUMN FROM $MASTER_TABLE_NAME WHERE" +
134fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                " $MASTER_TABLE_ID_COLUMN = $MASTER_TABLE_ID LIMIT 1"
135fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    }
136fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar
137fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    private fun createMasterTableQuery() : String {
138fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        return "CREATE TABLE IF NOT EXISTS `$MASTER_TABLE_NAME`(" +
139fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                "$MASTER_TABLE_ID_COLUMN INTEGER PRIMARY KEY," +
140fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar                "$MASTER_TABLE_IDENTITY_HASH_COLUMN TEXT)"
141fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    }
142fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar
143fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    private fun dropMasterTableQuery() : String {
144fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar        return "DROP TABLE IF EXISTS `$MASTER_TABLE_NAME`"
145fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar    }
146fa3905934508aa143d899cb9b62b3b074748c9e9Yigit Boyar
147d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    private fun createOnCreate() : MethodSpec {
148d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        return MethodSpec.methodBuilder("onCreate").apply {
149d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addModifiers(PUBLIC)
150d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addParameter(SupportDbTypeNames.DB, "_db")
151dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar            writeCreateStatements()
152d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        }.build()
153d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    }
154d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar
155d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    private fun createOnUpgrade() : MethodSpec {
156d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        return MethodSpec.methodBuilder("onUpgrade").apply {
157d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addModifiers(PUBLIC)
158d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addParameter(SupportDbTypeNames.DB, "_db")
159d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addParameter(TypeName.INT, "_oldVersion")
160d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addParameter(TypeName.INT, "_newVersion")
161c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar            database.entities.forEach {
162c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar                addStatement("_db.execSQL($S)", createDropTableQuery(it))
163c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar            }
164dc18ce63fe07921b1080e48d3e597e2b5240d17aYigit Boyar            writeCreateStatements()
165d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        }.build()
166d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    }
167d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar
168d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    private fun createOnDowngrade() : MethodSpec {
169d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        return MethodSpec.methodBuilder("onDowngrade").apply {
170d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addModifiers(PUBLIC)
171d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addParameter(SupportDbTypeNames.DB, "_db")
172d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addParameter(TypeName.INT, "_oldVersion")
173d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar            addParameter(TypeName.INT, "_newVersion")
174c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar            // TODO better handle this
175c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar            addStatement("onUpgrade(_db, _oldVersion, _newVersion)")
176d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar        }.build()
177d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar    }
178c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar
17988865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar
180c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar
181c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar    @VisibleForTesting
182c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar    fun createQuery(entity : Entity) : String {
18388865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar        return entity.createTableQuery
184c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar    }
185c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar
186c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar    @VisibleForTesting
187c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar    fun createDropTableQuery(entity: Entity) : String {
188c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar        return "DROP TABLE IF EXISTS `${entity.tableName}`"
189c678b3a4eebc102a1a3b5923c5e07478c0eecae3Yigit Boyar    }
190d72e20e472815b7d0918e0d309cee48a71c7988bYigit Boyar}
191