Предварительно упакованная база данных имеет недопустимую схему, несмотря на то, что она не использует createFromFile/Ресурс

#android #android-sqlite #android-room

Вопрос:

Я повсюду искал причину этой ошибки:

 TableInfo{name='recipes', columns={cook_time=Column{name='cook_time', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, tier_rating=Column{name='tier_rating', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, notes=Column{name='notes', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, tom_rating=Column{name='tom_rating', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, prep_time=Column{name='prep_time', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, serves=Column{name='serves', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='1'}, name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, url=Column{name='url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[Index{name='index_recipes_name', unique=true, columns=[name]}]}  Found: TableInfo{name='recipes', columns={}, foreignKeys=[], indices=[]} at androidx.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:163)  at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:135)  at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen(FrameworkSQLiteOpenHelper.java:195)  at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:428)  at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:317)  at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)  at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)  at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:622)  at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:399)  at androidx.room.RoomDatabase.query(RoomDatabase.java:442)  at androidx.room.util.DBUtil.query(DBUtil.java:83)  at com.example.shoppinglistapp2.db.dao.IngListItemDao_Impl$10.call(IngListItemDao_Impl.java:1036)  at com.example.shoppinglistapp2.db.dao.IngListItemDao_Impl$10.call(IngListItemDao_Impl.java:1033)  at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)  

Каждая другая аналогичная проблема, по-видимому, имеет по крайней мере НЕКОТОРЫЕ элементы таблицы или использует функцию databaseBuilder.createFromAsset. Я, однако, нет, мой код базы данных вставлен ниже. Я попытался удалить все свои миграции и просто вернуться к разрушительной миграции. Я просмотрел экспортированные файлы схем, которые четко отображают все свойства правильно. В моем приложении все работало нормально, пока я не попытался установить его на новое устройство, на котором не было существующего экземпляра базы данных. Я могу нормально запустить его на устройстве с существующими данными базы данных из предыдущих сборок/использования приложения, но новые установки выходят из строя сразу при запуске, и я получаю вышеуказанную ошибку. Я не пробовал этого раньше, поэтому не знаю, в какой момент могла возникнуть эта проблема.

Я использовал версию комнаты «2.4.0-alpha01», но я также попытался вернуться к версии 2.3.0 без каких-либо изменений. Может ли быть какая-то внешняя библиотека, которая пытается предварительно заполнить, о которой я не знаю? Я не уверен, где он даже находит схему с пустыми таблицами, отображаемыми в разделе «найдено» — у меня даже нет папки /assets, из которой он мог бы пытаться получить createFromAsset ресурс, даже если бы я это называл.

SlaDatabase.java

 @Database(entities = {Recipe.class, IngList.class, IngListItem.class, Tag.class, MealPlan.class, Meal.class},  version = 17,  exportSchema = true // autoMigrations = { // @AutoMigration(from = 15,to = 16), // @AutoMigration(from = 16, to = 17) // } ) public abstract class SlaDatabase extends RoomDatabase {  public abstract RecipeDao recipeDao();  public abstract IngListItemDao ingListItemDao();  public abstract MealDao mealDao();  public abstract TagDao tagDao();  public abstract MealPlanDao mealPlanDao();  public abstract IngListDao ingListDao();   private static volatile SlaDatabase INSTANCE;  private static final int NUMBER_OF_THREADS = 4;  static final ListeningExecutorService databaseWriteExecutor =  MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(NUMBER_OF_THREADS));   static SlaDatabase getDatabase(final Context context) {  if (INSTANCE == null) {  synchronized (SlaDatabase.class) {  if (INSTANCE == null) {  INSTANCE = Room.databaseBuilder(context.getApplicationContext(),  SlaDatabase.class, "sla_database")  .fallbackToDestructiveMigration()  .addCallback(sRoomDatabaseCallback)  .build();  }  }  }  return INSTANCE;  }   //ensure the shopping list IngList (id = 0) exists  private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback(){  @Override  public void onCreate(@NonNull SupportSQLiteDatabase db) {  super.onCreate(db);  db.beginTransaction();  try {  db.execSQL("INSERT OR IGNORE INTO ing_lists DEFAULT VALUES");  } catch(Exception e){  Log.d("TOM_TEST", e.toString());;  }  finally {  db.endTransaction();  }  }  };  

Recipe.java

 @Entity(  tableName = "recipes",  indices = {@Index(value = {"name"}, unique = true)}//the recipe name must be unique ) public class Recipe {   @PrimaryKey(autoGenerate = true)  private int id;   @NonNull  private String name;   @ColumnInfo(name="prep_time")  private int prepTime;   @ColumnInfo(name = "cook_time")  private int cookTime;   @ColumnInfo(defaultValue = "1")  private int serves = 1;   private String url;  private String notes;   private int tom_rating;  private int tier_rating;  //getters and setters...  

Exported schema for version 17 of SlaDatabase

 {  "formatVersion": 1,  "database": {  "version": 17,  "identityHash": "fe979e62da4f94c5bb89144e955361e0",  "entities": [  {  "tableName": "recipes",  "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `prep_time` INTEGER NOT NULL, `cook_time` INTEGER NOT NULL, `serves` INTEGER NOT NULL DEFAULT 1, `url` TEXT, `notes` TEXT, `tom_rating` INTEGER NOT NULL, `tier_rating` INTEGER NOT NULL)",  "fields": [  {  "fieldPath": "id",  "columnName": "id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "name",  "columnName": "name",  "affinity": "TEXT",  "notNull": true  },  {  "fieldPath": "prepTime",  "columnName": "prep_time",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "cookTime",  "columnName": "cook_time",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "serves",  "columnName": "serves",  "affinity": "INTEGER",  "notNull": true,  "defaultValue": "1"  },  {  "fieldPath": "url",  "columnName": "url",  "affinity": "TEXT",  "notNull": false  },  {  "fieldPath": "notes",  "columnName": "notes",  "affinity": "TEXT",  "notNull": false  },  {  "fieldPath": "tom_rating",  "columnName": "tom_rating",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "tier_rating",  "columnName": "tier_rating",  "affinity": "INTEGER",  "notNull": true  }  ],  "primaryKey": {  "columnNames": [  "id"  ],  "autoGenerate": true  },  "indices": [  {  "name": "index_recipes_name",  "unique": true,  "columnNames": [  "name"  ],  "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recipes_name` ON `${TABLE_NAME}` (`name`)"  }  ],  "foreignKeys": []  },  {  "tableName": "ing_lists",  "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` INTEGER, `meal_plan_id` INTEGER, FOREIGN KEY(`recipe_id`) REFERENCES `recipes`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`meal_plan_id`) REFERENCES `meal_plans`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",  "fields": [  {  "fieldPath": "id",  "columnName": "id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "recipeId",  "columnName": "recipe_id",  "affinity": "INTEGER",  "notNull": false  },  {  "fieldPath": "mealPlanId",  "columnName": "meal_plan_id",  "affinity": "INTEGER",  "notNull": false  }  ],  "primaryKey": {  "columnNames": [  "id"  ],  "autoGenerate": true  },  "indices": [  {  "name": "index_ing_lists_recipe_id",  "unique": false,  "columnNames": [  "recipe_id"  ],  "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_lists_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"  },  {  "name": "index_ing_lists_meal_plan_id",  "unique": false,  "columnNames": [  "meal_plan_id"  ],  "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_lists_meal_plan_id` ON `${TABLE_NAME}` (`meal_plan_id`)"  }  ],  "foreignKeys": [  {  "table": "recipes",  "onDelete": "CASCADE",  "onUpdate": "NO ACTION",  "columns": [  "recipe_id"  ],  "referencedColumns": [  "id"  ]  },  {  "table": "meal_plans",  "onDelete": "CASCADE",  "onUpdate": "NO ACTION",  "columns": [  "meal_plan_id"  ],  "referencedColumns": [  "id"  ]  }  ]  },  {  "tableName": "ing_list_items",  "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `list_id` INTEGER NOT NULL, `volume_unit` TEXT, `volume_qty` REAL NOT NULL, `mass_unit` TEXT, `mass_qty` REAL NOT NULL, `whole_item_qty` REAL NOT NULL, `other_unit` TEXT, `other_qty` REAL NOT NULL, `checked` INTEGER NOT NULL DEFAULT 0, `list_order` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`list_id`) REFERENCES `ing_lists`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",  "fields": [  {  "fieldPath": "id",  "columnName": "id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "name",  "columnName": "name",  "affinity": "TEXT",  "notNull": false  },  {  "fieldPath": "listId",  "columnName": "list_id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "volumeUnit",  "columnName": "volume_unit",  "affinity": "TEXT",  "notNull": false  },  {  "fieldPath": "volumeQty",  "columnName": "volume_qty",  "affinity": "REAL",  "notNull": true  },  {  "fieldPath": "massUnit",  "columnName": "mass_unit",  "affinity": "TEXT",  "notNull": false  },  {  "fieldPath": "massQty",  "columnName": "mass_qty",  "affinity": "REAL",  "notNull": true  },  {  "fieldPath": "wholeItemQty",  "columnName": "whole_item_qty",  "affinity": "REAL",  "notNull": true  },  {  "fieldPath": "otherUnit",  "columnName": "other_unit",  "affinity": "TEXT",  "notNull": false  },  {  "fieldPath": "otherQty",  "columnName": "other_qty",  "affinity": "REAL",  "notNull": true  },  {  "fieldPath": "checked",  "columnName": "checked",  "affinity": "INTEGER",  "notNull": true,  "defaultValue": "0"  },  {  "fieldPath": "listOrder",  "columnName": "list_order",  "affinity": "INTEGER",  "notNull": true,  "defaultValue": "0"  }  ],  "primaryKey": {  "columnNames": [  "id"  ],  "autoGenerate": true  },  "indices": [  {  "name": "index_ing_list_items_name",  "unique": false,  "columnNames": [  "name"  ],  "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_list_items_name` ON `${TABLE_NAME}` (`name`)"  },  {  "name": "index_ing_list_items_list_id",  "unique": false,  "columnNames": [  "list_id"  ],  "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_list_items_list_id` ON `${TABLE_NAME}` (`list_id`)"  }  ],  "foreignKeys": [  {  "table": "ing_lists",  "onDelete": "CASCADE",  "onUpdate": "NO ACTION",  "columns": [  "list_id"  ],  "referencedColumns": [  "id"  ]  }  ]  },  {  "tableName": "tags",  "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `recipe_id` INTEGER NOT NULL, FOREIGN KEY(`recipe_id`) REFERENCES `recipes`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",  "fields": [  {  "fieldPath": "id",  "columnName": "id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "name",  "columnName": "name",  "affinity": "TEXT",  "notNull": true  },  {  "fieldPath": "recipeId",  "columnName": "recipe_id",  "affinity": "INTEGER",  "notNull": true  }  ],  "primaryKey": {  "columnNames": [  "id"  ],  "autoGenerate": true  },  "indices": [  {  "name": "index_tags_recipe_id",  "unique": false,  "columnNames": [  "recipe_id"  ],  "createSql": "CREATE INDEX IF NOT EXISTS `index_tags_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"  }  ],  "foreignKeys": [  {  "table": "recipes",  "onDelete": "CASCADE",  "onUpdate": "NO ACTION",  "columns": [  "recipe_id"  ],  "referencedColumns": [  "id"  ]  }  ]  },  {  "tableName": "meal_plans",  "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT)",  "fields": [  {  "fieldPath": "id",  "columnName": "id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "name",  "columnName": "name",  "affinity": "TEXT",  "notNull": false  }  ],  "primaryKey": {  "columnNames": [  "id"  ],  "autoGenerate": true  },  "indices": [],  "foreignKeys": []  },  {  "tableName": "meals",  "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `plan_id` INTEGER NOT NULL, `day_id` INTEGER NOT NULL, `day_title` TEXT, `recipe_id` INTEGER, `notes` TEXT, FOREIGN KEY(`recipe_id`) REFERENCES `recipes`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`plan_id`) REFERENCES `meal_plans`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",  "fields": [  {  "fieldPath": "id",  "columnName": "id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "planId",  "columnName": "plan_id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "dayId",  "columnName": "day_id",  "affinity": "INTEGER",  "notNull": true  },  {  "fieldPath": "dayTitle",  "columnName": "day_title",  "affinity": "TEXT",  "notNull": false  },  {  "fieldPath": "recipeId",  "columnName": "recipe_id",  "affinity": "INTEGER",  "notNull": false  },  {  "fieldPath": "notes",  "columnName": "notes",  "affinity": "TEXT",  "notNull": false  }  ],  "primaryKey": {  "columnNames": [  "id"  ],  "autoGenerate": true  },  "indices": [  {  "name": "index_meals_plan_id",  "unique": false,  "columnNames": [  "plan_id"  ],  "createSql": "CREATE INDEX IF NOT EXISTS `index_meals_plan_id` ON `${TABLE_NAME}` (`plan_id`)"  },  {  "name": "index_meals_recipe_id",  "unique": false,  "columnNames": [  "recipe_id"  ],  "createSql": "CREATE INDEX IF NOT EXISTS `index_meals_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"  }  ],  "foreignKeys": [  {  "table": "recipes",  "onDelete": "SET NULL",  "onUpdate": "NO ACTION",  "columns": [  "recipe_id"  ],  "referencedColumns": [  "id"  ]  },  {  "table": "meal_plans",  "onDelete": "CASCADE",  "onUpdate": "NO ACTION",  "columns": [  "plan_id"  ],  "referencedColumns": [  "id"  ]  }  ]  }  ],  "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, 'fe979e62da4f94c5bb89144e955361e0')"  ]  } }  

Ответ №1:

Вы завершаете транзакцию без использования setTransactionSuccessful . Без endTransaction этого будет откат изменений, эффективно отменяя изменения, включая создание таблиц, созданных Комнатой.

Таким образом, из-за отката первая проверка таблицы не находит таблицы (Рецепта), которая соответствует ожидаемой таблице (Рецепта), следовательно, найденная таблица не содержит столбцов и т. Д.

  • В нем говорится о предварительно упакованной базе данных, потому что Room, по-видимому, предполагает, что эта несоответствующая база данных должна была быть откуда-то взята (это не то, что создаст Room); как таковая, она немного вводит в заблуждение (но что она должна сказать? (риторический)).

Вам необходимо использовать/применить setTransactionSuccessful() метод, например

 private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {  @Override  public void onCreate(@NonNull SupportSQLiteDatabase db) {  super.onCreate(db);  db.beginTransaction();  try {  db.execSQL("INSERT OR IGNORE INTO ing_lists DEFAULT VALUES");  } catch (Exception e) {  Log.d("TOM_TEST", e.toString());  } finally {  db.setTransactionSuccessful(); //lt;lt;lt;lt;lt;lt;lt;lt;lt;lt;  db.endTransaction();  }  } };  

Альтернативным решением было бы не использовать транзакцию, например

 private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {  @Override  public void onCreate(@NonNull SupportSQLiteDatabase db) {  super.onCreate(db);  try {  db.execSQL("INSERT OR IGNORE INTO ing_lists DEFAULT VALUES");  } catch (Exception e) {  Log.d("TOM_TEST", e.toString());  }  } };  

В этом нет никакого недостатка, поскольку выполнение одной инструкции SQL само по себе является транзакцией.