Home | History | Annotate | Download | only in migration
      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.migration;
     18 
     19 
     20 import static org.hamcrest.CoreMatchers.equalTo;
     21 import static org.hamcrest.CoreMatchers.is;
     22 import static org.hamcrest.MatcherAssert.assertThat;
     23 
     24 import static java.util.Arrays.asList;
     25 import static java.util.Collections.singletonList;
     26 
     27 import android.support.test.InstrumentationRegistry;
     28 import android.support.test.filters.SmallTest;
     29 import android.support.test.runner.AndroidJUnit4;
     30 import android.util.Pair;
     31 
     32 import androidx.room.util.TableInfo;
     33 import androidx.sqlite.db.SupportSQLiteDatabase;
     34 import androidx.sqlite.db.SupportSQLiteOpenHelper;
     35 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
     36 
     37 import org.junit.After;
     38 import org.junit.Test;
     39 import org.junit.runner.RunWith;
     40 
     41 import java.io.IOException;
     42 import java.util.Arrays;
     43 import java.util.Collections;
     44 import java.util.HashMap;
     45 import java.util.HashSet;
     46 import java.util.List;
     47 import java.util.Map;
     48 import java.util.Set;
     49 
     50 @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
     51 @RunWith(AndroidJUnit4.class)
     52 @SmallTest
     53 public class TableInfoTest {
     54     private SupportSQLiteDatabase mDb;
     55 
     56     @Test
     57     public void readSimple() {
     58         mDb = createDatabase(
     59                 "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
     60                         + "name TEXT)");
     61         TableInfo info = TableInfo.read(mDb, "foo");
     62         assertThat(info, is(new TableInfo("foo",
     63                 toMap(new TableInfo.Column("id", "INTEGER", false, 1),
     64                         new TableInfo.Column("name", "TEXT", false, 0)),
     65                 Collections.<TableInfo.ForeignKey>emptySet())));
     66     }
     67 
     68     @Test
     69     public void multiplePrimaryKeys() {
     70         mDb = createDatabase(
     71                 "CREATE TABLE foo (id INTEGER,"
     72                         + "name TEXT, PRIMARY KEY(name, id))");
     73         TableInfo info = TableInfo.read(mDb, "foo");
     74         assertThat(info, is(new TableInfo("foo",
     75                 toMap(new TableInfo.Column("id", "INTEGER", false, 2),
     76                         new TableInfo.Column("name", "TEXT", false, 1)),
     77                 Collections.<TableInfo.ForeignKey>emptySet())));
     78     }
     79 
     80     @Test
     81     public void alteredTable() {
     82         mDb = createDatabase(
     83                 "CREATE TABLE foo (id INTEGER,"
     84                         + "name TEXT, PRIMARY KEY(name))");
     85         mDb.execSQL("ALTER TABLE foo ADD COLUMN added REAL;");
     86         TableInfo info = TableInfo.read(mDb, "foo");
     87         assertThat(info, is(new TableInfo("foo",
     88                 toMap(new TableInfo.Column("id", "INTEGER", false, 0),
     89                         new TableInfo.Column("name", "TEXT", false, 1),
     90                         new TableInfo.Column("added", "REAL", false, 0)),
     91                 Collections.<TableInfo.ForeignKey>emptySet())));
     92     }
     93 
     94     @Test
     95     public void nonNull() {
     96         mDb = createDatabase(
     97                 "CREATE TABLE foo (name TEXT NOT NULL)");
     98         TableInfo info = TableInfo.read(mDb, "foo");
     99         assertThat(info, is(new TableInfo("foo",
    100                 toMap(new TableInfo.Column("name", "TEXT", true, 0)),
    101                 Collections.<TableInfo.ForeignKey>emptySet())));
    102     }
    103 
    104     @Test
    105     public void defaultValue() {
    106         mDb = createDatabase(
    107                 "CREATE TABLE foo (name TEXT DEFAULT blah)");
    108         TableInfo info = TableInfo.read(mDb, "foo");
    109         assertThat(info, is(new TableInfo(
    110                 "foo",
    111                 toMap(new TableInfo.Column("name", "TEXT", false, 0)),
    112                 Collections.<TableInfo.ForeignKey>emptySet())));
    113     }
    114 
    115     @Test
    116     public void foreignKey() {
    117         mDb = createDatabase(
    118                 "CREATE TABLE foo (name TEXT)",
    119                 "CREATE TABLE bar(barName TEXT, FOREIGN KEY(barName) REFERENCES foo(name))"
    120         );
    121         TableInfo info = TableInfo.read(mDb, "bar");
    122         assertThat(info.foreignKeys.size(), is(1));
    123         final TableInfo.ForeignKey foreignKey = info.foreignKeys.iterator().next();
    124         assertThat(foreignKey.columnNames, is(singletonList("barName")));
    125         assertThat(foreignKey.referenceColumnNames, is(singletonList("name")));
    126         assertThat(foreignKey.onDelete, is("NO ACTION"));
    127         assertThat(foreignKey.onUpdate, is("NO ACTION"));
    128         assertThat(foreignKey.referenceTable, is("foo"));
    129     }
    130 
    131     @Test
    132     public void multipleForeignKeys() {
    133         mDb = createDatabase(
    134                 "CREATE TABLE foo (name TEXT, lastName TEXT)",
    135                 "CREATE TABLE foo2 (name TEXT, lastName TEXT)",
    136                 "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
    137                         + " FOREIGN KEY(barName) REFERENCES foo(name) ON UPDATE SET NULL,"
    138                         + " FOREIGN KEY(barLastName) REFERENCES foo2(lastName) ON DELETE CASCADE)");
    139         TableInfo info = TableInfo.read(mDb, "bar");
    140         assertThat(info.foreignKeys.size(), is(2));
    141         Set<TableInfo.ForeignKey> expected = new HashSet<>();
    142         expected.add(new TableInfo.ForeignKey("foo2", // table
    143                 "CASCADE", // on delete
    144                 "NO ACTION", // on update
    145                 singletonList("barLastName"), // my
    146                 singletonList("lastName")) // ref
    147         );
    148         expected.add(new TableInfo.ForeignKey("foo", // table
    149                 "NO ACTION", // on delete
    150                 "SET NULL", // on update
    151                 singletonList("barName"), // mine
    152                 singletonList("name")/*ref*/));
    153         assertThat(info.foreignKeys, equalTo(expected));
    154     }
    155 
    156     @Test
    157     public void compositeForeignKey() {
    158         mDb = createDatabase(
    159                 "CREATE TABLE foo (name TEXT, lastName TEXT)",
    160                 "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
    161                         + " FOREIGN KEY(barName, barLastName) REFERENCES foo(name, lastName)"
    162                         + " ON UPDATE cascade ON DELETE RESTRICT)");
    163         TableInfo info = TableInfo.read(mDb, "bar");
    164         assertThat(info.foreignKeys.size(), is(1));
    165         TableInfo.ForeignKey expected = new TableInfo.ForeignKey(
    166                 "foo", // table
    167                 "RESTRICT", // on delete
    168                 "CASCADE", // on update
    169                 asList("barName", "barLastName"), // my columns
    170                 asList("name", "lastName") // ref columns
    171         );
    172         assertThat(info.foreignKeys.iterator().next(), is(expected));
    173     }
    174 
    175     @Test
    176     public void caseInsensitiveTypeName() {
    177         mDb = createDatabase(
    178                 "CREATE TABLE foo (n integer)");
    179         TableInfo info = TableInfo.read(mDb, "foo");
    180         assertThat(info, is(new TableInfo(
    181                 "foo",
    182                 toMap(new TableInfo.Column("n", "INTEGER", false, 0)),
    183                 Collections.<TableInfo.ForeignKey>emptySet())));
    184     }
    185 
    186     @Test
    187     public void readIndices() {
    188         mDb = createDatabase(
    189                 "CREATE TABLE foo (n INTEGER, indexed TEXT, unique_indexed TEXT,"
    190                         + "a INTEGER, b INTEGER);",
    191                 "CREATE INDEX foo_indexed ON foo(indexed);",
    192                 "CREATE UNIQUE INDEX foo_unique_indexed ON foo(unique_indexed COLLATE NOCASE"
    193                         + " DESC);",
    194                 "CREATE INDEX " + TableInfo.Index.DEFAULT_PREFIX + "foo_composite_indexed"
    195                         + " ON foo(a, b);"
    196         );
    197         TableInfo info = TableInfo.read(mDb, "foo");
    198         assertThat(info, is(new TableInfo(
    199                 "foo",
    200                 toMap(new TableInfo.Column("n", "INTEGER", false, 0),
    201                         new TableInfo.Column("indexed", "TEXT", false, 0),
    202                         new TableInfo.Column("unique_indexed", "TEXT", false, 0),
    203                         new TableInfo.Column("a", "INTEGER", false, 0),
    204                         new TableInfo.Column("b", "INTEGER", false, 0)),
    205                 Collections.<TableInfo.ForeignKey>emptySet(),
    206                 toSet(new TableInfo.Index("index_foo_blahblah", false,
    207                         Arrays.asList("a", "b")),
    208                         new TableInfo.Index("foo_unique_indexed", true,
    209                                 Arrays.asList("unique_indexed")),
    210                         new TableInfo.Index("foo_indexed", false,
    211                                 Arrays.asList("indexed"))))
    212         ));
    213     }
    214 
    215     @Test
    216     public void compatColumnTypes() {
    217         // see:https://www.sqlite.org/datatype3.html 3.1
    218         List<Pair<String, String>> testCases = Arrays.asList(
    219                 new Pair<>("TINYINT", "integer"),
    220                 new Pair<>("VARCHAR", "text"),
    221                 new Pair<>("DOUBLE", "real"),
    222                 new Pair<>("BOOLEAN", "numeric"),
    223                 new Pair<>("FLOATING POINT", "integer")
    224         );
    225         for (Pair<String, String> testCase : testCases) {
    226             mDb = createDatabase(
    227                     "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
    228                             + "name " + testCase.first + ")");
    229             TableInfo info = TableInfo.read(mDb, "foo");
    230             assertThat(info, is(new TableInfo("foo",
    231                     toMap(new TableInfo.Column("id", "INTEGER", false, 1),
    232                             new TableInfo.Column("name", testCase.second, false, 0)),
    233                     Collections.<TableInfo.ForeignKey>emptySet())));
    234         }
    235     }
    236 
    237     private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
    238         Map<String, TableInfo.Column> result = new HashMap<>();
    239         for (TableInfo.Column column : columns) {
    240             result.put(column.name, column);
    241         }
    242         return result;
    243     }
    244 
    245     private static <T> Set<T> toSet(T... ts) {
    246         final HashSet<T> result = new HashSet<T>();
    247         for (T t : ts) {
    248             result.add(t);
    249         }
    250         return result;
    251     }
    252 
    253     @After
    254     public void closeDb() throws IOException {
    255         if (mDb != null && mDb.isOpen()) {
    256             mDb.close();
    257         }
    258     }
    259 
    260     private static SupportSQLiteDatabase createDatabase(final String... queries) {
    261         return new FrameworkSQLiteOpenHelperFactory().create(
    262                 SupportSQLiteOpenHelper.Configuration
    263                         .builder(InstrumentationRegistry.getTargetContext())
    264                         .name(null)
    265                         .callback(new SupportSQLiteOpenHelper.Callback(1) {
    266                             @Override
    267                             public void onCreate(SupportSQLiteDatabase db) {
    268                                 for (String query : queries) {
    269                                     db.execSQL(query);
    270                                 }
    271                             }
    272 
    273                             @Override
    274                             public void onUpgrade(SupportSQLiteDatabase db, int oldVersion,
    275                                     int newVersion) {
    276                                 throw new IllegalStateException("should not be upgrading");
    277                             }
    278                         }).build()
    279         ).getWritableDatabase();
    280     }
    281 }
    282