1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.room.verifier 18 19 import androidx.room.parser.Collate 20 import androidx.room.parser.SQLTypeAffinity 21 import androidx.room.processor.Context 22 import androidx.room.testing.TestInvocation 23 import androidx.room.vo.CallType 24 import androidx.room.vo.Constructor 25 import androidx.room.vo.Database 26 import androidx.room.vo.Entity 27 import androidx.room.vo.Field 28 import androidx.room.vo.FieldGetter 29 import androidx.room.vo.FieldSetter 30 import androidx.room.vo.PrimaryKey 31 import collect 32 import columnNames 33 import org.hamcrest.CoreMatchers.`is` 34 import org.hamcrest.CoreMatchers.hasItem 35 import org.hamcrest.CoreMatchers.notNullValue 36 import org.hamcrest.MatcherAssert.assertThat 37 import org.junit.Test 38 import org.junit.runner.RunWith 39 import org.junit.runners.Parameterized 40 import org.mockito.Mockito.doReturn 41 import org.mockito.Mockito.mock 42 import simpleRun 43 import java.sql.Connection 44 import javax.lang.model.element.Element 45 import javax.lang.model.element.ExecutableElement 46 import javax.lang.model.element.TypeElement 47 import javax.lang.model.type.DeclaredType 48 import javax.lang.model.type.PrimitiveType 49 import javax.lang.model.type.TypeKind 50 import javax.lang.model.type.TypeMirror 51 52 @RunWith(Parameterized::class) 53 class DatabaseVerifierTest(private val useLocalizedCollation: Boolean) { 54 @Test 55 fun testSimpleDatabase() { 56 simpleRun { invocation -> 57 val verifier = createVerifier(invocation) 58 val stmt = verifier.connection.createStatement() 59 val rs = stmt.executeQuery("select * from sqlite_master WHERE type='table'") 60 assertThat( 61 rs.collect { set -> set.getString("name") }, hasItem(`is`("User"))) 62 val table = verifier.connection.prepareStatement("select * from User") 63 assertThat(table.columnNames(), `is`(listOf("id", "name", "lastName", "ratio"))) 64 65 assertThat(getPrimaryKeys(verifier.connection, "User"), `is`(listOf("id"))) 66 }.compilesWithoutError() 67 } 68 69 private fun createVerifier(invocation: TestInvocation): DatabaseVerifier { 70 return DatabaseVerifier.create(invocation.context, mock(Element::class.java), 71 userDb(invocation.context).entities)!! 72 } 73 74 @Test 75 fun testFullEntityQuery() { 76 validQueryTest("select * from User") { 77 assertThat(it, `is`( 78 QueryResultInfo(listOf( 79 ColumnInfo("id", SQLTypeAffinity.INTEGER), 80 ColumnInfo("name", SQLTypeAffinity.TEXT), 81 ColumnInfo("lastName", SQLTypeAffinity.TEXT), 82 ColumnInfo("ratio", SQLTypeAffinity.REAL) 83 )))) 84 } 85 } 86 87 @Test 88 fun testPartialFields() { 89 validQueryTest("select id, lastName from User") { 90 assertThat(it, `is`( 91 QueryResultInfo(listOf( 92 ColumnInfo("id", SQLTypeAffinity.INTEGER), 93 ColumnInfo("lastName", SQLTypeAffinity.TEXT) 94 )))) 95 } 96 } 97 98 @Test 99 fun testRenamedField() { 100 validQueryTest("select id as myId, lastName from User") { 101 assertThat(it, `is`( 102 QueryResultInfo(listOf( 103 ColumnInfo("myId", SQLTypeAffinity.INTEGER), 104 ColumnInfo("lastName", SQLTypeAffinity.TEXT) 105 )))) 106 } 107 } 108 109 @Test 110 fun testGrouped() { 111 validQueryTest("select MAX(ratio) from User GROUP BY name") { 112 assertThat(it, `is`( 113 QueryResultInfo(listOf( 114 // unfortunately, we don't get this information 115 ColumnInfo("MAX(ratio)", SQLTypeAffinity.NULL) 116 )))) 117 } 118 } 119 120 @Test 121 fun testConcat() { 122 validQueryTest("select name || lastName as mergedName from User") { 123 assertThat(it, `is`( 124 QueryResultInfo(listOf( 125 // unfortunately, we don't get this information 126 ColumnInfo("mergedName", SQLTypeAffinity.NULL) 127 )))) 128 } 129 } 130 131 @Test 132 fun testResultWithArgs() { 133 validQueryTest("select id, name || lastName as mergedName from User where name LIKE ?") { 134 assertThat(it, `is`( 135 QueryResultInfo(listOf( 136 // unfortunately, we don't get this information 137 ColumnInfo("id", SQLTypeAffinity.INTEGER), 138 ColumnInfo("mergedName", SQLTypeAffinity.NULL) 139 )))) 140 } 141 } 142 143 @Test 144 fun testDeleteQuery() { 145 validQueryTest("delete from User where name LIKE ?") { 146 assertThat(it, `is`(QueryResultInfo(emptyList()))) 147 } 148 } 149 150 @Test 151 fun testUpdateQuery() { 152 validQueryTest("update User set name = ? WHERE id = ?") { 153 assertThat(it, `is`(QueryResultInfo(emptyList()))) 154 } 155 } 156 157 @Test 158 fun testBadQuery() { 159 simpleRun { invocation -> 160 val verifier = createVerifier(invocation) 161 val (_, error) = verifier.analyze("select foo from User") 162 assertThat(error, notNullValue()) 163 }.compilesWithoutError() 164 } 165 166 @Test 167 fun testCollate() { 168 validQueryTest("SELECT id, name FROM user ORDER BY name COLLATE LOCALIZED ASC") { 169 assertThat(it, `is`( 170 QueryResultInfo(listOf( 171 // unfortunately, we don't get this information 172 ColumnInfo("id", SQLTypeAffinity.INTEGER), 173 ColumnInfo("name", SQLTypeAffinity.TEXT) 174 )))) 175 } 176 } 177 178 @Test 179 fun testCollateBasQuery() { 180 simpleRun { invocation -> 181 val verifier = createVerifier(invocation) 182 val (_, error) = verifier.analyze( 183 "SELECT id, name FROM user ORDER BY name COLLATE LOCALIZEDASC") 184 assertThat(error, notNullValue()) 185 }.compilesWithoutError() 186 } 187 188 private fun validQueryTest(sql: String, cb: (QueryResultInfo) -> Unit) { 189 simpleRun { invocation -> 190 val verifier = createVerifier(invocation) 191 val info = verifier.analyze(sql) 192 cb(info) 193 }.compilesWithoutError() 194 } 195 196 private fun userDb(context: Context): Database { 197 return database(entity("User", 198 field("id", primitive(context, TypeKind.INT), SQLTypeAffinity.INTEGER), 199 field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT), 200 field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT), 201 field("ratio", primitive(context, TypeKind.FLOAT), SQLTypeAffinity.REAL))) 202 } 203 204 private fun database(vararg entities: Entity): Database { 205 return Database( 206 element = mock(TypeElement::class.java), 207 type = mock(TypeMirror::class.java), 208 entities = entities.toList(), 209 daoMethods = emptyList(), 210 version = -1, 211 exportSchema = false, 212 enableForeignKeys = false) 213 } 214 215 private fun entity(tableName: String, vararg fields: Field): Entity { 216 return Entity( 217 element = mock(TypeElement::class.java), 218 tableName = tableName, 219 type = mock(DeclaredType::class.java), 220 fields = fields.toList(), 221 embeddedFields = emptyList(), 222 indices = emptyList(), 223 primaryKey = PrimaryKey(null, fields.take(1), false), 224 foreignKeys = emptyList(), 225 constructor = Constructor(mock(ExecutableElement::class.java), emptyList()) 226 ) 227 } 228 229 private fun field(name: String, type: TypeMirror, affinity: SQLTypeAffinity): Field { 230 val element = mock(Element::class.java) 231 doReturn(type).`when`(element).asType() 232 val f = Field( 233 element = element, 234 name = name, 235 type = type, 236 columnName = name, 237 affinity = affinity, 238 collate = if (useLocalizedCollation && affinity == SQLTypeAffinity.TEXT) { 239 Collate.LOCALIZED 240 } else { 241 null 242 } 243 ) 244 assignGetterSetter(f, name, type) 245 return f 246 } 247 248 private fun assignGetterSetter(f: Field, name: String, type: TypeMirror) { 249 f.getter = FieldGetter(name, type, CallType.FIELD) 250 f.setter = FieldSetter(name, type, CallType.FIELD) 251 } 252 253 private fun primitive(context: Context, kind: TypeKind): PrimitiveType { 254 return context.processingEnv.typeUtils.getPrimitiveType(kind) 255 } 256 257 private fun getPrimaryKeys(connection: Connection, tableName: String): List<String> { 258 val stmt = connection.createStatement() 259 val resultSet = stmt.executeQuery("PRAGMA table_info($tableName)") 260 return resultSet.collect { 261 Pair(it.getString("name"), it.getInt("pk")) 262 } 263 .filter { it.second > 0 } 264 .sortedBy { it.second } 265 .map { it.first } 266 } 267 268 companion object { 269 @Parameterized.Parameters(name = "useLocalizedCollation={0}") 270 @JvmStatic 271 fun params() = arrayListOf(true, false) 272 } 273 } 274