Home | History | Annotate | Download | only in verifier
      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