Don't export hidden FTS columns in schema bundles.

FTS tables always have a hidden rowid table and an optional
languageid. When exporting the schema to a bundle that gets
serialized to a JSON, we shouldn't include such columns
because during verification they will not be available when
executing PRAGMA table_info.

This fixes users of the testing library that had migrations
to an FTS table with either a named rowid column or a languageid
column.

Bug: 145858914
Test: FtsMigrationTest
Change-Id: I3ff09337c861fdca6056c2bce29f2873dd8166c1
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/FtsEntity.kt b/room/compiler/src/main/kotlin/androidx/room/vo/FtsEntity.kt
index e884247..0cbc0d1 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/FtsEntity.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/FtsEntity.kt
@@ -129,7 +129,7 @@
     override fun toBundle() = FtsEntityBundle(
             tableName,
             createTableQuery(BundleUtil.TABLE_NAME_PLACEHOLDER),
-            fields.map { it.toBundle() },
+            nonHiddenFields.map { it.toBundle() },
             primaryKey.toBundle(),
             ftsVersion.name,
             ftsOptions.toBundle(),
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.FtsMigrationTest.FtsMigrationDb/5.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.FtsMigrationTest.FtsMigrationDb/5.json
new file mode 100644
index 0000000..dc4c90d
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.FtsMigrationTest.FtsMigrationDb/5.json
@@ -0,0 +1,203 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 5,
+    "identityHash": "c87397851f6aa4b9a35f9982eff36d52",
+    "entities": [
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS3",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Book",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`title` TEXT, `author` TEXT, `numOfPages` INTEGER NOT NULL, `text` TEXT, matchinfo=fts3)",
+        "fields": [
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "author",
+            "columnName": "author",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "numOfPages",
+            "columnName": "numOfPages",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "text",
+            "columnName": "text",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "User",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `firstName` TEXT, `lastName` TEXT, `line1` TEXT, `line2` TEXT, `state` TEXT, `zipcode` INTEGER, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstName",
+            "columnName": "firstName",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "lastName",
+            "columnName": "lastName",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.line1",
+            "columnName": "line1",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.line2",
+            "columnName": "line2",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.state",
+            "columnName": "state",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.zipcode",
+            "columnName": "zipcode",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "User",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_BEFORE_UPDATE BEFORE UPDATE ON `User` BEGIN DELETE FROM `AddressFts` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_BEFORE_DELETE BEFORE DELETE ON `User` BEGIN DELETE FROM `AddressFts` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_AFTER_UPDATE AFTER UPDATE ON `User` BEGIN INSERT INTO `AddressFts`(`docid`, `line1`, `line2`, `state`, `zipcode`) VALUES (NEW.`rowid`, NEW.`line1`, NEW.`line2`, NEW.`state`, NEW.`zipcode`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_AFTER_INSERT AFTER INSERT ON `User` BEGIN INSERT INTO `AddressFts`(`docid`, `line1`, `line2`, `state`, `zipcode`) VALUES (NEW.`rowid`, NEW.`line1`, NEW.`line2`, NEW.`state`, NEW.`zipcode`); END"
+        ],
+        "tableName": "AddressFts",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`line1` TEXT, `line2` TEXT, `state` TEXT, `zipcode` INTEGER, content=`User`)",
+        "fields": [
+          {
+            "fieldPath": "address.line1",
+            "columnName": "line1",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.line2",
+            "columnName": "line2",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.state",
+            "columnName": "state",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.zipcode",
+            "columnName": "zipcode",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Mail",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`content` TEXT)",
+        "fields": [
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "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, 'c87397851f6aa4b9a35f9982eff36d52')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.FtsMigrationTest.FtsMigrationDb/6.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.FtsMigrationTest.FtsMigrationDb/6.json
new file mode 100644
index 0000000..f7e2055
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.FtsMigrationTest.FtsMigrationDb/6.json
@@ -0,0 +1,203 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 6,
+    "identityHash": "3fd7863a508b4c82d9ad4f0f8b86ee34",
+    "entities": [
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS3",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Book",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`title` TEXT, `author` TEXT, `numOfPages` INTEGER NOT NULL, `text` TEXT, matchinfo=fts3)",
+        "fields": [
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "author",
+            "columnName": "author",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "numOfPages",
+            "columnName": "numOfPages",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "text",
+            "columnName": "text",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "User",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `firstName` TEXT, `lastName` TEXT, `line1` TEXT, `line2` TEXT, `state` TEXT, `zipcode` INTEGER, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstName",
+            "columnName": "firstName",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "lastName",
+            "columnName": "lastName",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.line1",
+            "columnName": "line1",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.line2",
+            "columnName": "line2",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.state",
+            "columnName": "state",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.zipcode",
+            "columnName": "zipcode",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "User",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_BEFORE_UPDATE BEFORE UPDATE ON `User` BEGIN DELETE FROM `AddressFts` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_BEFORE_DELETE BEFORE DELETE ON `User` BEGIN DELETE FROM `AddressFts` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_AFTER_UPDATE AFTER UPDATE ON `User` BEGIN INSERT INTO `AddressFts`(`docid`, `line1`, `line2`, `state`, `zipcode`) VALUES (NEW.`rowid`, NEW.`line1`, NEW.`line2`, NEW.`state`, NEW.`zipcode`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_AddressFts_AFTER_INSERT AFTER INSERT ON `User` BEGIN INSERT INTO `AddressFts`(`docid`, `line1`, `line2`, `state`, `zipcode`) VALUES (NEW.`rowid`, NEW.`line1`, NEW.`line2`, NEW.`state`, NEW.`zipcode`); END"
+        ],
+        "tableName": "AddressFts",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`line1` TEXT, `line2` TEXT, `state` TEXT, `zipcode` INTEGER, content=`User`)",
+        "fields": [
+          {
+            "fieldPath": "address.line1",
+            "columnName": "line1",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.line2",
+            "columnName": "line2",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.state",
+            "columnName": "state",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "address.zipcode",
+            "columnName": "zipcode",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "lid",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Mail",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`content` TEXT, languageid=`lid`)",
+        "fields": [
+          {
+            "fieldPath": "content",
+            "columnName": "content",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "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, '3fd7863a508b4c82d9ad4f0f8b86ee34')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/FtsMigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/FtsMigrationTest.java
index 9abf50d..6fbc18c 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/FtsMigrationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/FtsMigrationTest.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.os.Build;
 
+import androidx.annotation.NonNull;
+import androidx.room.ColumnInfo;
 import androidx.room.Dao;
 import androidx.room.Database;
 import androidx.room.Embedded;
@@ -63,7 +65,7 @@
                 FtsMigrationDb.class.getCanonicalName());
     }
 
-    @Database(entities = {Book.class, User.class, AddressFts.class}, version = 4)
+    @Database(entities = {Book.class, User.class, AddressFts.class, Mail.class}, version = 6)
     abstract static class FtsMigrationDb extends RoomDatabase {
         abstract BookDao getBookDao();
         abstract UserDao getUserDao();
@@ -120,6 +122,16 @@
         public int zipcode;
     }
 
+    @Entity
+    @Fts4(languageId = "lid")
+    static class Mail {
+        @PrimaryKey
+        @ColumnInfo(name = "rowid")
+        public long mailId;
+        public String content;
+        public int lid;
+    }
+
     @Test
     public void validMigration() throws Exception {
         SupportSQLiteDatabase db;
@@ -145,7 +157,8 @@
         try {
             Context targetContext = ApplicationProvider.getApplicationContext();
             FtsMigrationDb db = Room.databaseBuilder(targetContext, FtsMigrationDb.class, TEST_DB)
-                    .addMigrations(BAD_MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
+                    .addMigrations(BAD_MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
+                            MIGRATION_5_6)
                     .build();
             helper.closeWhenFinished(db);
             db.getBookDao().getAllBooks();
@@ -170,6 +183,26 @@
         assertThat(addresses.get(0).line1, is("Ruth Ave"));
     }
 
+    @Test
+    public void validFtsWithNamedRowIdMigration() throws Exception {
+        SupportSQLiteDatabase db;
+
+        db = helper.createDatabase(TEST_DB, 4);
+        db.close();
+
+        helper.runMigrationsAndValidate(TEST_DB, 5, true, MIGRATION_4_5);
+    }
+
+    @Test
+    public void validFtsWithLanguageIdMigration() throws Exception {
+        SupportSQLiteDatabase db;
+
+        db = helper.createDatabase(TEST_DB, 5);
+        db.close();
+
+        helper.runMigrationsAndValidate(TEST_DB, 6, true, MIGRATION_5_6);
+    }
+
     @SuppressWarnings("deprecation")
     private FtsMigrationDb getLatestDb() {
         FtsMigrationDb db = Room.databaseBuilder(
@@ -220,6 +253,23 @@
         }
     };
 
+    private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
+        @Override
+        public void migrate(@NonNull SupportSQLiteDatabase database) {
+            database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `Mail` USING FTS4("
+                    + "`content` TEXT NOT NULL)");
+        }
+    };
+
+    private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
+        @Override
+        public void migrate(@NonNull SupportSQLiteDatabase database) {
+            database.execSQL("DROP TABLE `Mail`");
+            database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `Mail` USING FTS4("
+                    + "`content` TEXT NOT NULL, languageid=`lid`)");
+        }
+    };
+
     private static final Migration BAD_MIGRATION_1_2 = new Migration(1, 2) {
         @Override
         public void migrate(SupportSQLiteDatabase database) {
@@ -230,6 +280,6 @@
     };
 
     private static final Migration[] ALL_MIGRATIONS = new Migration[]{
-            MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4
+            MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6
     };
 }