From cff209cd44b13dd16b63c9c080d6217ad98d1e55 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 7 Sep 2016 22:02:57 +1000 Subject: [PATCH 1/4] Replicated issue #763 in a test case --- .../org/fdroid/fdroid/Issue763MultiRepo.java | 138 ++++++++++++++++++ .../fdroid/fdroid/MultiRepoUpdaterTest.java | 18 ++- app/src/test/resources/index.antox.jar | Bin 0 -> 5206 bytes app/src/test/resources/index.microg.jar | Bin 0 -> 5132 bytes 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java create mode 100644 app/src/test/resources/index.antox.jar create mode 100644 app/src/test/resources/index.microg.jar diff --git a/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java b/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java new file mode 100644 index 000000000..8df75aedb --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java @@ -0,0 +1,138 @@ +package org.fdroid.fdroid; + +import android.content.ContentValues; + +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.data.Schema; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +// TODO: Use sdk=24 when Robolectric supports this +@Config(constants = BuildConfig.class, sdk = 23) +@RunWith(RobolectricGradleTestRunner.class) +public class Issue763MultiRepo extends MultiRepoUpdaterTest { + + private Repo microGRepo; + private Repo antoxRepo; + + @Before + public void setup() { + String microGCert = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37"; + microGRepo = createRepo("MicroG", "https://microg.org/fdroid/repo", context, microGCert); + + String antoxCert = "308204f1308202d9a0030201020204565444c6300d06092a864886f70d01010b050030293110300e060355040b1307462d44726f6964311530130603550403130c706b672e746f782e63686174301e170d3136303131323037353333395a170d3433303533303037353333395a30293110300e060355040b1307462d44726f6964311530130603550403130c706b672e746f782e6368617430820222300d06092a864886f70d01010105000382020f003082020a0282020100a16c61417d25545d681d7c01acea881f268bb4d708099aa12143f12d24a18afe120f532901efc3a26137915bba5ffd4e8f0d21783965b2c207593b44002e6ed7ca6cbb124829c134950c9c76388d70cdb5c1ac37581687f1a4a51ce7d5c0a21ab000bae2c14572d26693de8b33726852e262ec9c85bed5d6a1e236977862d8e796a3722bc69346ae27951527963165469ab4ec9c8a38ccabd4c3de718eeaa8ff054bcd04374ac46af7cf011d97bd2625c4c7f6c2851b0cab8446eaf2f94a2b1506aafdfb192d0a31ede495fd4cc4e122f92daa500806d29aa3f63e51dbf6f00fb37979b4b70543f019d55e95173165378983517f2d2811fe79d491f09dc2568794c3a09f539986d4489b939d65c26a83f6b6165976c00cd648d081afc3a5eeabd1c4e3c0ae42d92197f4086caca8bcfe939036d02f84e815b95842da27daea297bd098507f806012fe0cae2d4dd38bd876a7efb0c173bfae260e820d56e7026afe4f8806d12ffa2e75da33d178472625414d2ed3e0fe9ef1f2ba0b26b877960bea54c8e32f36540758712b40775b56ef1149db3df17202e5163733df12a48011f9077e34aebeba2fe11dc578ce12bd26a30a1d458d0172378b530de118dc46823e17f6bbb7d0163483531e12d416f6f9d90d63b6c248a30058fb1c0b1c78a50601fdad0ca22dbfa470059251f37ce558a7bacf2ebad2d810049717aee16c8d530203010001a321301f301d0603551d0e0416041419f45074c35bffc3bdc4ebee34966db52cfcb54e300d06092a864886f70d01010b05000382020100426ce3f1a5944dbd41e6abc5348e32f220f46b58a8794091e5de1f6248af18e42c0b1ed9c5196b27c9fbd0aa59170653f3044b0f8cd60f027f888be91fdb52fb7e6f3c125bbbf968ca1d43fede1a47a82ebd89ef37e2abc5931f2475ed7c3b95707c75f1e90e0f08460288ee090e5136a4dd682ddf8755b6d2c8e8ff58037865d69f198599371cca60e6ab8cac7c35de1edfaff2730a8c91489e30c7d770fe7b2299b41229d27989bf20260d43c8c077e53ec11e1a17b8879ec8c995fb9fa178d6ffdd6629c3104601368cc76e0f10f7ad3a2a729f92d219700dd44a8621a8102ec61e28d534b518633b4edb125966e80bf006f0e1258f7bd36357ab2ebdb8fb40fb1616f75bf5db2689b3910dff266084832159afc1571454a8070fe2a02389254e5f3cbab933a57117cb76bb615c4180c88f3bd04c6f23ea75ac05ab81dd4ddd2c9f2b3eb94d54682f12c7838612f434b00a6da678e9e82b5b4a18c037929a622773c6bc4bcf1eb45872c998248d98812d6be1a77d0d70182b9b2296b8802fbd0fb2f04b78bfafbaae756940f777ae43f63e8f47e97618063fc743ba1dd3d37bb434581fb3487420dd893fa474d87b94923102c12a680ede3107c680be0d8d5e7f89c0033a740f0ba3e1563baddb540f0f9154653b003d9dd3cbdd649e808ecfec9dd8d9949ccabb7b8b7ad287023bc8ca12dad9892158da300a25de07aaca"; + antoxRepo = createRepo("Tox", "https://pkg.tox.chat/fdroid/repo", context, antoxCert); + } + + public void setEnabled(Repo repo, boolean enabled) { + ContentValues values = new ContentValues(1); + values.put(Schema.RepoTable.Cols.IN_USE, enabled); + RepoProvider.Helper.update(context, repo, values); + } + + @Test + public void antoxRepo() throws RepoUpdater.UpdateException { + assertAntoxEmpty(); + setEnabled(microGRepo, true); + updateAntox(); + assertAntoxExists(); + } + + private void updateAntox() throws RepoUpdater.UpdateException { + updateRepo(new RepoUpdater(context, antoxRepo), "index.antox.jar"); + } + + @Test + public void microGRepo() throws RepoUpdater.UpdateException { + assertMicroGEmpty(); + setEnabled(microGRepo, true); + updateMicroG(); + assertMicroGExists(); + } + + private void updateMicroG() throws RepoUpdater.UpdateException { + updateRepo(new RepoUpdater(context, microGRepo), "index.microg.jar"); + } + + @Test + public void antoxAndMicroG() throws RepoUpdater.UpdateException { + assertMicroGEmpty(); + assertAntoxEmpty(); + + setEnabled(microGRepo, true); + setEnabled(antoxRepo, true); + updateMicroG(); + updateAntox(); + assertMicroGExists(); + assertAntoxExists(); + + setEnabled(microGRepo, false); + RepoProvider.Helper.purgeApps(context, microGRepo); + assertMicroGEmpty(); + assertAntoxExists(); + + setEnabled(microGRepo, true); + updateMicroG(); + assertMicroGExists(); + assertAntoxExists(); + + setEnabled(antoxRepo, false); + RepoProvider.Helper.purgeApps(context, antoxRepo); + assertMicroGExists(); + assertAntoxEmpty(); + + setEnabled(antoxRepo, true); + updateAntox(); + assertMicroGExists(); + assertAntoxExists(); + } + + private void assertAntoxEmpty() { + List actualApksBeforeUpdate = ApkProvider.Helper.findByRepo(context, antoxRepo, Schema.ApkTable.Cols.ALL); + assertEquals(0, actualApksBeforeUpdate.size()); + } + + private void assertMicroGEmpty() { + List actualApksBeforeUpdate = ApkProvider.Helper.findByRepo(context, microGRepo, Schema.ApkTable.Cols.ALL); + assertEquals(0, actualApksBeforeUpdate.size()); + } + + private void assertAntoxExists() { + String packageName = "chat.tox.antox"; + List actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, antoxRepo, Schema.ApkTable.Cols.ALL); + int[] expectedVersions = new int[] {15421}; + + assertApp(packageName, expectedVersions); + assertApksExist(actualApksAfterUpdate, packageName, expectedVersions); + } + + private void assertMicroGExists() { + List actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, microGRepo, Schema.ApkTable.Cols.ALL); + + String vendingPackage = "com.android.vending"; + int[] expectedVendingVersions = new int[] {1}; + assertApp(vendingPackage, expectedVendingVersions); + assertApksExist(actualApksAfterUpdate, vendingPackage, expectedVendingVersions); + + String gmsPackage = "com.google.android.gms"; + int[] expectedGmsVersions = new int[] {9452267, 9452266, 9452265, 9258262, 9258259, 9258258, 8492252, }; + assertApp(gmsPackage, expectedGmsVersions); + assertApksExist(actualApksAfterUpdate, gmsPackage, expectedGmsVersions); + + String gsfPackage = "com.google.android.gsf"; + int[] expectedGsfVersions = new int[] {8}; + assertApp(gsfPackage, expectedGsfVersions); + assertApksExist(actualApksAfterUpdate, gsfPackage, expectedGsfVersions); + } + +} diff --git a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java index 9b694e863..f4c529059 100644 --- a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java @@ -152,9 +152,13 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { } } - private RepoUpdater createUpdater(String name, String uri, Context context) { + protected Repo createRepo(String name, String uri, Context context) { + return createRepo(name, uri, context, PUB_KEY); + } + + protected Repo createRepo(String name, String uri, Context context, String signingCert) { Repo repo = new Repo(); - repo.signingCertificate = PUB_KEY; + repo.signingCertificate = signingCert; repo.address = uri; repo.name = name; @@ -167,7 +171,11 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { // Need to reload the repo based on address so that it includes the primary key from // the database. - return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address)); + return RepoProvider.Helper.findByAddress(context, repo.address); + } + + protected RepoUpdater createUpdater(String name, String uri, Context context) { + return new RepoUpdater(context, createRepo(name, uri, context)); } protected boolean updateConflicting() throws UpdateException { @@ -182,13 +190,13 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { return updateRepo(createUpdater(REPO_ARCHIVE, REPO_ARCHIVE_URI, context), "multiRepo.archive.jar"); } - private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { + protected boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { File indexJar = TestUtils.copyResourceToTempFile(indexJarPath); try { updater.processDownloadedFile(indexJar); } finally { if (indexJar != null && indexJar.exists()) { - assertTrue(indexJar.delete()); + indexJar.delete(); } } return true; diff --git a/app/src/test/resources/index.antox.jar b/app/src/test/resources/index.antox.jar new file mode 100644 index 0000000000000000000000000000000000000000..ab864b28fb6104ddc056e218930041e4a728ea35 GIT binary patch literal 5206 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 literal 0 HcmV?d00001 From 9b13d98943f00da1c5c890b4239cd799fb678a17 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sat, 24 Sep 2016 07:16:36 +1000 Subject: [PATCH 2/4] Use database constant instead of hard coded string literal --- app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index 3f73997af..7b696241c 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -69,7 +69,7 @@ public class TempAppProvider extends AppProvider { } private AppQuerySelection queryApps(String packageNames) { - return queryApps(packageNames, getTableName() + ".id"); + return queryApps(packageNames, getTableName() + "." + AppMetadataTable.Cols.PACKAGE_NAME); } public static class Helper { From 1ba6034e1961c0bd09f50340f39d2895c4706f20 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sat, 24 Sep 2016 07:16:51 +1000 Subject: [PATCH 3/4] Fixed issue #763 by being more specific when creating temp table for update. When performing the old style `CREATE TABLE ... AS SELECT ...` (CTAS) statement, no indexes are added. In addition, rowid is not added. Even if manually specifying an autoincrement column in the original schema, this autoincrement column does not get recreated with the CTAS statement. So instead, this change reuses the original `CREATE TABLE` statement which explicitly defines all of the relevant columns. In addition, it explicitly adds an autoincrement integer primary key. This has the same semantics as the existing implicit `rowid` column that sqlite creates. From from https://sqlite.org/autoinc.html: > In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID > (except in WITHOUT ROWID tables) which is always a 64-bit signed integer. However, as it is explicit now, is copied when doing the `INSERT INTO ... SELECT ...` statement to get data from the real table to the temp table in preperation for updates (and back again after the update has populated the temp table). Note that this makes the `INSERT INTO ... SELECT ...` statements slightly more brittle, because now we need the table definition used to create the temp table (from `DBHelper.CREATE_APP_TABLE`) to have the same column order as those in the real `fdroid_app` table. While this may sound like a silly comment to make, it is important because database migrations can result in a database having the correct set of columns, but in a different order to how they were specified in the original create table statement. If a database migration performs an `ALTER TABLE ... ADD COLUMN ...` the column will be added at the end. If at the same time the `CREATE TABLE` is changed so that the new column is specified as the second to last column in the list of columns, then the `INSERT INTO ... SELECT ...` will not work as expected. --- app/src/main/java/org/fdroid/fdroid/data/DBHelper.java | 2 +- app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index c92b32e17..f0769f84e 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -69,7 +69,7 @@ class DBHelper extends SQLiteOpenHelper { + "PRIMARY KEY (" + ApkTable.Cols.APP_ID + ", " + ApkTable.Cols.VERSION_CODE + ", " + ApkTable.Cols.REPO_ID + ")" + ");"; - private static final String CREATE_TABLE_APP = "CREATE TABLE " + AppMetadataTable.NAME + static final String CREATE_TABLE_APP = "CREATE TABLE " + AppMetadataTable.NAME + " ( " + AppMetadataTable.Cols.PACKAGE_NAME + " text not null, " + AppMetadataTable.Cols.NAME + " text not null, " diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index 7b696241c..af7aa91f1 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -161,7 +161,8 @@ public class TempAppProvider extends AppProvider { final SQLiteDatabase db = db(); ensureTempTableDetached(db); db.execSQL("ATTACH DATABASE ':memory:' AS " + DB); - db.execSQL("CREATE TABLE " + DB + "." + getTableName() + " AS SELECT * FROM main." + AppMetadataTable.NAME); + db.execSQL(DBHelper.CREATE_TABLE_APP.replaceFirst(AppMetadataTable.NAME, DB + "." + getTableName())); + db.execSQL("INSERT INTO " + DB + "." + getTableName() + " SELECT * FROM " + AppMetadataTable.NAME); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (" + AppMetadataTable.Cols.PACKAGE_NAME + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_upstreamVercode ON " + getTableName() + " (" + AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_compatible ON " + getTableName() + " (" + AppMetadataTable.Cols.IS_COMPATIBLE + ");"); From 1678223cabed4da1dd3f155bc8569d5b2376ffba Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sat, 24 Sep 2016 08:10:01 +1000 Subject: [PATCH 4/4] More robust fix for #763, specifying column names to copy explicitly. This is far less brittle at runtime, but slightly more work at dev time. The following things are undesirable but make it much easier to write: * Use of `CREATE_TABLE_APP.replaceFirst(...)` to create the temp tables. * Having to specify a list fo columns twice in `Schema` (`ALL_COLS` + `COLS`). The `replaceFirst` means we don't need to maintain two separate create table statements. It is a little messy because there is no compile time guarantee that we are creating a valid SQL statement at the end, just our knowledge that a create table statment tends to have the table name first and it probably wont cause problems. The `ALL_COLS` + `COLS` is required so that we don't have to type out a list of fields when copying data in `TempAppProvider`. Otherwise, whenever a new column is added, developers would need to know that it also needs to be added to this third place. Currently it is in the `Schema` and the `CREATE_TABLE_*` statements where one needs to add a new column. These are both intuitive and hopefully easily discoverable. Having to add it to the `TempAppProvider` is less intuitive and likely to result in bugs. --- .../java/org/fdroid/fdroid/data/DBHelper.java | 2 +- .../java/org/fdroid/fdroid/data/Schema.java | 32 +++++++++++++++++++ .../fdroid/fdroid/data/TempApkProvider.java | 3 +- .../fdroid/fdroid/data/TempAppProvider.java | 15 +++++++-- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index f0769f84e..1397c44b4 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -45,7 +45,7 @@ class DBHelper extends SQLiteOpenHelper { + RepoTable.Cols.TIMESTAMP + " integer not null default 0" + ");"; - private static final String CREATE_TABLE_APK = + static final String CREATE_TABLE_APK = "CREATE TABLE " + ApkTable.NAME + " ( " + ApkTable.Cols.APP_ID + " integer not null, " + ApkTable.Cols.VERSION_NAME + " text not null, " diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index 07eb59d30..88987b1de 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -76,6 +76,25 @@ public interface Schema { String SIGNATURE = "installedSig"; } + /** + * Each of the physical columns in the sqlite table. Differs from {@link Cols#ALL} in + * that it doesn't include fields which are aliases of other fields (e.g. {@link Cols#_ID} + * or which are from other related tables (e.g. {@link Cols.SuggestedApk#VERSION_NAME}). + */ + String[] ALL_COLS = { + ROW_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION, + LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL, + CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID, + UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED, + CATEGORIES, ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE, + SUGGESTED_VERSION_CODE, + }; + + /** + * Superset of {@link Cols#ALL_COLS} including fields from other tables and also an alias + * to satisfy the Android requirement for an "_ID" field. + * @see Cols#ALL_COLS + */ String[] ALL = { _ID, ROW_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION, LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL, @@ -134,6 +153,19 @@ public interface Schema { String PACKAGE_NAME = "appPackageName"; } + /** + * @see AppMetadataTable.Cols#ALL_COLS + */ + String[] ALL_COLS = { + APP_ID, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, + SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION, + PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, + IS_COMPATIBLE, INCOMPATIBLE_REASONS, + }; + + /** + * @see AppMetadataTable.Cols#ALL + */ String[] ALL = { _ID, APP_ID, App.PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION, diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java index db9d03b65..8cc83f773 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java @@ -125,7 +125,8 @@ public class TempApkProvider extends ApkProvider { private void initTable() { final SQLiteDatabase db = db(); final String memoryDbName = TempAppProvider.DB; - db.execSQL("CREATE TABLE " + memoryDbName + "." + getTableName() + " AS SELECT * FROM main." + ApkTable.NAME); + db.execSQL(DBHelper.CREATE_TABLE_APK.replaceFirst(Schema.ApkTable.NAME, memoryDbName + "." + getTableName())); + db.execSQL(TempAppProvider.copyData(Schema.ApkTable.Cols.ALL_COLS, Schema.ApkTable.NAME, memoryDbName + "." + getTableName())); db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_vercode on " + getTableName() + " (" + ApkTable.Cols.VERSION_CODE + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (" + ApkTable.Cols.IS_COMPATIBLE + ");"); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index af7aa91f1..18520e286 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -162,12 +162,21 @@ public class TempAppProvider extends AppProvider { ensureTempTableDetached(db); db.execSQL("ATTACH DATABASE ':memory:' AS " + DB); db.execSQL(DBHelper.CREATE_TABLE_APP.replaceFirst(AppMetadataTable.NAME, DB + "." + getTableName())); - db.execSQL("INSERT INTO " + DB + "." + getTableName() + " SELECT * FROM " + AppMetadataTable.NAME); + db.execSQL(copyData(AppMetadataTable.Cols.ALL_COLS, AppMetadataTable.NAME, DB + "." + getTableName())); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (" + AppMetadataTable.Cols.PACKAGE_NAME + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_upstreamVercode ON " + getTableName() + " (" + AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_compatible ON " + getTableName() + " (" + AppMetadataTable.Cols.IS_COMPATIBLE + ");"); } + /** + * Constructs an INSERT INTO ... SELECT statement as a means from getting data from one table + * into another. The list of columns to copy are explicitly specified using colsToCopy. + */ + static String copyData(String[] colsToCopy, String fromTable, String toTable) { + String cols = TextUtils.join(", ", colsToCopy); + return "INSERT INTO " + toTable + " (" + cols + ") SELECT " + cols + " FROM " + fromTable; + } + private void commitTable() { final SQLiteDatabase db = db(); try { @@ -177,10 +186,10 @@ public class TempAppProvider extends AppProvider { final String tempApk = DB + "." + TempApkProvider.TABLE_TEMP_APK; db.execSQL("DELETE FROM " + AppMetadataTable.NAME + " WHERE 1"); - db.execSQL("INSERT INTO " + AppMetadataTable.NAME + " SELECT * FROM " + tempApp); + db.execSQL(copyData(AppMetadataTable.Cols.ALL_COLS, tempApp, AppMetadataTable.NAME)); db.execSQL("DELETE FROM " + ApkTable.NAME + " WHERE 1"); - db.execSQL("INSERT INTO " + ApkTable.NAME + " SELECT * FROM " + tempApk); + db.execSQL(copyData(ApkTable.Cols.ALL_COLS, tempApk, ApkTable.NAME)); db.setTransactionSuccessful();