diff --git a/CHANGELOG.md b/CHANGELOG.md index a35b2a1c72..d9cf2e5808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This release is compatible with the Realm Object Server 3.0.0-beta.3 or later. * `realm.subscribeForObjects()` have been removed. Use `RealmQuery.findAllAsync(String subscriptionName)` and `RealmQuery.findAllAsync()` instead. * Removed previously deprecated `RealmQuery.findAllSorted()`, `RealmQuery.findAllSortedAsync()` `RealmQuery.distinct() and `RealmQuery.distinctAsync()`. * Renamed `RealmQuery.distinctValues()` to `RealmQuery.distinct()` +* Removing workarounds for old Realms versions [0.80.1](https://github.com/realm/realm-java/issues/1059), [0.84.1](https://github.com/realm/realm-java/issues/1703) and [2.0.0](https://github.com/realm/realm-java/pull/3488). ### Enhancements diff --git a/realm/realm-library/src/androidTest/assets/080_annotationtypes.realm b/realm/realm-library/src/androidTest/assets/080_annotationtypes.realm deleted file mode 100644 index 1252c79172..0000000000 Binary files a/realm/realm-library/src/androidTest/assets/080_annotationtypes.realm and /dev/null differ diff --git a/realm/realm-library/src/androidTest/assets/0841_annotationtypes.realm b/realm/realm-library/src/androidTest/assets/0841_annotationtypes.realm deleted file mode 100644 index c605500aab..0000000000 Binary files a/realm/realm-library/src/androidTest/assets/0841_annotationtypes.realm and /dev/null differ diff --git a/realm/realm-library/src/androidTest/assets/0841_pk_migration.realm b/realm/realm-library/src/androidTest/assets/0841_pk_migration.realm deleted file mode 100644 index 2b55bc51aa..0000000000 Binary files a/realm/realm-library/src/androidTest/assets/0841_pk_migration.realm and /dev/null differ diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java index 25f5176911..cb7067a28a 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmTests.java @@ -1159,8 +1159,7 @@ public boolean shouldCompact(long totalBytes, long usedBytes) { assertEquals(1, compactOnLaunchCount.get()); realm = Realm.getInstance(realmConfig); - // Called 2 more times. The PK table migration logic (the old PK bug) needs to open/close the Realm once. - assertEquals(3, compactOnLaunchCount.get()); + assertEquals(2, compactOnLaunchCount.get()); Thread thread = new Thread(new Runnable() { @Override @@ -1168,7 +1167,7 @@ public void run() { Realm bgRealm = Realm.getInstance(realmConfig); bgRealm.close(); // compactOnLaunch should not be called anymore! - assertEquals(3, compactOnLaunchCount.get()); + assertEquals(2, compactOnLaunchCount.get()); } }); thread.start(); @@ -1181,7 +1180,7 @@ public void run() { realm.close(); - assertEquals(3, compactOnLaunchCount.get()); + assertEquals(2, compactOnLaunchCount.get()); } @Test diff --git a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java index 04c431318f..75ca14bbb0 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/internal/PrimaryKeyTests.java @@ -25,10 +25,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - import io.realm.DynamicRealm; import io.realm.DynamicRealmObject; import io.realm.FieldAttribute; @@ -38,9 +34,7 @@ import io.realm.RealmSchema; import io.realm.rule.TestRealmConfigurationFactory; -import static junit.framework.Assert.assertFalse; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @@ -170,92 +164,4 @@ public void addEmptyRowWithPrimaryKeyLong() { assertEquals(42L, row.getLong(0)); sharedRealm.cancelTransaction(); } - - @Test - public void migratePrimaryKeyTableIfNeeded_first() throws IOException { - configFactory.copyRealmFromAssets(context, "080_annotationtypes.realm", "default.realm"); - sharedRealm = OsSharedRealm.getInstance(config); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - Table t = sharedRealm.getTable("class_AnnotationTypes"); - assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); - assertEquals(RealmFieldType.STRING, sharedRealm.getTable("pk").getColumnType(0)); - } - - @Test - public void migratePrimaryKeyTableIfNeeded_second() throws IOException { - configFactory.copyRealmFromAssets(context, "0841_annotationtypes.realm", "default.realm"); - sharedRealm = OsSharedRealm.getInstance(config); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - Table t = sharedRealm.getTable("class_AnnotationTypes"); - assertEquals("id", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "AnnotationTypes")); - assertEquals("AnnotationTypes", sharedRealm.getTable("pk").getString(0, 0)); - } - - // See https://github.com/realm/realm-java/issues/1775 - // Before 0.84.2, pk table added prefix "class_" to every class's name. - // After 0.84.2, the pk table should be migrated automatically to remove the "class_". - // In 0.84.2, the class names in pk table has been renamed to some incorrect names like "Thclass", "Mclass", - // "NClass", "Meclass" and etc.. - // The 0841_pk_migration.realm is made to produce the issue. - @Test - public void migratePrimaryKeyTableIfNeeded_primaryKeyTableMigratedWithRightName() throws IOException { - List tableNames = Arrays.asList( - "ChatList", "Drafts", "Member", "Message", "Notifs", "NotifyLink", "PopularPost", - "Post", "Tags", "Threads", "User"); - - configFactory.copyRealmFromAssets(context, "0841_pk_migration.realm", "default.realm"); - sharedRealm = OsSharedRealm.getInstance(config); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - - Table table = sharedRealm.getTable("pk"); - for (int i = 0; i < table.size(); i++) { - UncheckedRow row = table.getUncheckedRow(i); - // io_realm_internal_Table_PRIMARY_KEY_CLASS_COLUMN_INDEX 0LL - assertTrue(tableNames.contains(row.getString(0))); - } - } - - // PK table's column 'pk_table' needs search index in order to use set_string_unique. - // See https://github.com/realm/realm-java/pull/3488 - @Test - public void migratePrimaryKeyTableIfNeeded_primaryKeyTableNeedSearchIndex() { - sharedRealm = OsSharedRealm.getInstance(config); - sharedRealm.beginTransaction(); - OsObjectStore.setSchemaVersion(sharedRealm,0); // Create meta table - Table table = sharedRealm.createTable(Table.getTableNameForClass("TestTable")); - long column = table.addColumn(RealmFieldType.INTEGER, "PKColumn"); - table.addSearchIndex(column); - OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable", "PKColumn"); - sharedRealm.commitTransaction(); - - assertEquals("PKColumn", OsObjectStore.getPrimaryKeyForObject(sharedRealm, "TestTable")); - // Now we have a pk table with search index. - - sharedRealm.beginTransaction(); - Table pkTable = sharedRealm.getTable("pk"); - long classColumn = pkTable.getColumnIndex("pk_table"); - pkTable.removeSearchIndex(classColumn); - - // Tries to add a pk for another table. - Table table2 = sharedRealm.createTable(Table.getTableNameForClass("TestTable2")); - long column2 = table2.addColumn(RealmFieldType.INTEGER, "PKColumn"); - table2.addSearchIndex(column2); - try { - OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable2", "PKColumn"); - } catch (IllegalStateException ignored) { - // Column has no search index. - } - sharedRealm.commitTransaction(); - - assertFalse(pkTable.hasSearchIndex(classColumn)); - - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - assertTrue(pkTable.hasSearchIndex(classColumn)); - - sharedRealm.beginTransaction(); - // Now it works. - table2.addSearchIndex(column2); - OsObjectStore.setPrimaryKeyForObject(sharedRealm, "TestTable2", "PKColumn"); - sharedRealm.commitTransaction(); - } } diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp index d85ad5e1d9..bde5d75f13 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_Table.cpp @@ -1197,120 +1197,6 @@ JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeIsValid(JNIEnv*, j return to_jbool(TBL(nativeTablePtr)->is_attached()); // noexcept } -static bool pk_table_needs_migration(ConstTableRef pk_table) -{ - // Fix wrong types (string, int) -> (string, string) - if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { - return true; - } - - // If needed remove "class_" prefix from class names - size_t number_of_rows = pk_table->size(); - for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { - StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); - if (table_name.begins_with(TABLE_PREFIX)) { - return true; - } - } - // From realm-java 2.0.0, pk table's class column requires a search index. - if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { - return true; - } - return false; -} - -// 1) Fixes interop issue with Cocoa Realm where the Primary Key table had different types. -// This affects: -// - All Realms created by Cocoa and used by Realm-android up to 0.80.1 -// - All Realms created by Realm-Android 0.80.1 and below -// See https://github.com/realm/realm-java/issues/1059 -// -// 2) Fix interop issue with Cocoa Realm where primary key tables on Cocoa doesn't have the "class_" prefix. -// This affects: -// - All Realms created by Cocoa and used by Realm-android up to 0.84.1 -// - All Realms created by Realm-Android 0.84.1 and below -// See https://github.com/realm/realm-java/issues/1703 -// -// 3> PK table's column 'pk_table' needs search index in order to use set_string_unique. -// This affects: -// - All Realms created by Cocoa and used by Realm-java before 2.0.0 -// See https://github.com/realm/realm-java/pull/3488 - -// This methods converts the old (wrong) table format (string, integer) to the right (string,string) format and strips -// any class names in the col[0] of their "class_" prefix -static bool migrate_pk_table(const Group& group, TableRef pk_table) -{ - bool changed = false; - - // Fix wrong types (string, int) -> (string, string) - if (pk_table->get_column_type(FIELD_COLUMN_INDEX) == type_Int) { - StringData tmp_col_name = StringData("tmp_field_name"); - size_t tmp_col_ndx = pk_table->add_column(DataType(type_String), tmp_col_name); - - // Create tmp string column with field name instead of column index - size_t number_of_rows = pk_table->size(); - for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { - StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); - size_t col_ndx = static_cast(pk_table->get_int(FIELD_COLUMN_INDEX, row_ndx)); - StringData col_name = group.get_table(table_name)->get_column_name(col_ndx); - // Make a copy of the string - pk_table->set_string(tmp_col_ndx, row_ndx, col_name); - } - - // Delete old int column, and rename tmp column to same name - // The column index for the renamed column will then be the same as the deleted old column - pk_table->remove_column(FIELD_COLUMN_INDEX); - pk_table->rename_column(pk_table->get_column_index(tmp_col_name), StringData("pk_property")); - changed = true; - } - - // If needed remove "class_" prefix from class names - size_t number_of_rows = pk_table->size(); - for (size_t row_ndx = 0; row_ndx < number_of_rows; row_ndx++) { - StringData table_name = pk_table->get_string(CLASS_COLUMN_INDEX, row_ndx); - if (table_name.begins_with(TABLE_PREFIX)) { - // New string copy is needed, since the original memory will be changed. - std::string str(table_name.substr(TABLE_PREFIX.length())); - StringData sd(str); - pk_table->set_string(CLASS_COLUMN_INDEX, row_ndx, sd); - changed = true; - } - } - - // From realm-java 2.0.0, pk table's class column requires a search index. - if (!pk_table->has_search_index(CLASS_COLUMN_INDEX)) { - pk_table->add_search_index(CLASS_COLUMN_INDEX); - changed = true; - } - return changed; -} - -JNIEXPORT void JNICALL Java_io_realm_internal_Table_nativeMigratePrimaryKeyTableIfNeeded(JNIEnv* env, jclass, - jlong shared_realm_ptr) -{ - TR_ENTER_PTR(shared_realm_ptr) - auto& shared_realm = *reinterpret_cast(shared_realm_ptr); - try { - if (!shared_realm->read_group().has_table(PK_TABLE_NAME)) { - return; - } - - auto pk_table = shared_realm->read_group().get_table(PK_TABLE_NAME); - if (!pk_table_needs_migration(pk_table)) { - return; - } - - shared_realm->begin_transaction(); - if (migrate_pk_table(shared_realm->read_group(), pk_table)) { - shared_realm->commit_transaction(); - } - else { - shared_realm->cancel_transaction(); - } - } - CATCH_STD() -} - JNIEXPORT jboolean JNICALL Java_io_realm_internal_Table_nativeHasSameSchema(JNIEnv*, jobject, jlong thisTablePtr, jlong otherTablePtr) { diff --git a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java index c8e164bc64..26e14a7599 100644 --- a/realm/realm-library/src/main/java/io/realm/DynamicRealm.java +++ b/realm/realm-library/src/main/java/io/realm/DynamicRealm.java @@ -81,6 +81,11 @@ public void onResult(int count) { this.schema = new MutableRealmSchema(this); } + private DynamicRealm (RealmConfiguration configuration) { + super(configuration, null); + schema = null; + } + private DynamicRealm(OsSharedRealm sharedRealm) { super(sharedRealm); this.schema = new MutableRealmSchema(this); @@ -267,6 +272,16 @@ public void executeTransaction(Transaction transaction) { } } + /** + * Creates a schemaless {@link DynamicRealm} instance. + * + * @param configuration {@link RealmConfiguration} used to open the Realm. + * @return an empty Realm without any schema. + */ + public static DynamicRealm createSchemalessInstance(RealmConfiguration configuration) { + return new DynamicRealm(configuration); + } + /** * Creates a {@link DynamicRealm} instance without checking the existence in the {@link RealmCache}. * diff --git a/realm/realm-library/src/main/java/io/realm/RealmCache.java b/realm/realm-library/src/main/java/io/realm/RealmCache.java index 3dac06e938..8a6ec054cf 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmCache.java +++ b/realm/realm-library/src/main/java/io/realm/RealmCache.java @@ -36,9 +36,7 @@ import io.realm.internal.Capabilities; import io.realm.internal.ObjectServerFacade; import io.realm.internal.OsObjectStore; -import io.realm.internal.OsSharedRealm; import io.realm.internal.RealmNotifier; -import io.realm.internal.Table; import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.android.AndroidRealmNotifier; @@ -289,43 +287,6 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo if (getTotalGlobalRefCount() == 0) { copyAssetFileIfNeeded(configuration); - boolean fileExists = configuration.realmExists(); - - OsSharedRealm sharedRealm = null; - try { - if (configuration.isSyncConfiguration()) { - // If waitForInitialRemoteData() was enabled, we need to make sure that all data is downloaded - // before proceeding. We need to open the Realm instance first to start any potential underlying - // SyncSession so this will work. TODO: This needs to be decoupled. - if (!fileExists) { - sharedRealm = OsSharedRealm.getInstance(configuration); - try { - ObjectServerFacade.getSyncFacadeIfPossible().downloadRemoteChanges(configuration); - } catch (Throwable t) { - // If an error happened while downloading initial data, we need to reset the file so we can - // download it again on the next attempt. - sharedRealm.close(); - sharedRealm = null; - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now. - // https://github.com/realm/realm-java/issues/5416 - BaseRealm.deleteRealm(configuration); - throw t; - } - } - } else { - if (fileExists) { - // Primary key problem only exists before we release sync. - sharedRealm = OsSharedRealm.getInstance(configuration); - Table.migratePrimaryKeyTableIfNeeded(sharedRealm); - } - } - } finally { - if (sharedRealm != null) { - sharedRealm.close(); - } - } - // We are holding the lock, and we can set the invalidated configuration since there is no global ref to it. this.configuration = configuration; } else { @@ -336,6 +297,11 @@ private synchronized E doCreateRealmOrGetFromCache(RealmCo if (refAndCount.localRealm.get() == null) { // Creates a new local Realm instance BaseRealm realm; + boolean fileExists = configuration.realmExists(); + + if (configuration.isSyncConfiguration() && !fileExists) { + ObjectServerFacade.getSyncFacadeIfPossible().downloadRemoteChanges(configuration); + } if (realmClass == Realm.class) { // RealmMigrationNeededException might be thrown here. diff --git a/realm/realm-library/src/main/java/io/realm/internal/Table.java b/realm/realm-library/src/main/java/io/realm/internal/Table.java index 533603d665..4ebdda2e35 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/Table.java +++ b/realm/realm-library/src/main/java/io/realm/internal/Table.java @@ -513,22 +513,6 @@ public void removeSearchIndex(long columnIndex) { nativeRemoveSearchIndex(nativePtr, columnIndex); } - /* - * 1) Migration required to fix https://github.com/realm/realm-java/issues/1059 - * This will convert INTEGER column to the corresponding STRING column if needed. - * Any database created on Realm-Java 0.80.1 and below will have this error. - * - * 2) Migration required to fix: https://github.com/realm/realm-java/issues/1703 - * This will remove the prefix "class_" from all table names in the pk_column - * Any database created on Realm-Java 0.84.1 and below will have this error. - * - * The native method will begin a transaction and make the migration if needed. - * This function should not be called in a transaction. - */ - public static void migratePrimaryKeyTableIfNeeded(OsSharedRealm sharedRealm) { - nativeMigratePrimaryKeyTableIfNeeded(sharedRealm.getNativePtr()); - } - public boolean hasSearchIndex(long columnIndex) { return nativeHasSearchIndex(nativePtr, columnIndex); } @@ -780,8 +764,6 @@ public static String getTableNameForClass(String name) { public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); - private static native void nativeMigratePrimaryKeyTableIfNeeded(long sharedRealmPtr); - private native void nativeAddSearchIndex(long nativePtr, long columnIndex); private native void nativeRemoveSearchIndex(long nativePtr, long columnIndex); diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java index b0ac440bbf..db5e03a86e 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/SyncObjectServerFacade.java @@ -24,6 +24,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import io.realm.DynamicRealm; +import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.SyncConfiguration; import io.realm.SyncManager; @@ -33,6 +35,7 @@ import io.realm.exceptions.RealmException; import io.realm.internal.network.NetworkStateReceiver; import io.realm.internal.sync.permissions.ObjectPermissionsModule; +import io.realm.log.RealmLog; @SuppressWarnings({"unused", "WeakerAccess"}) // Used through reflection. See ObjectServerFacade @Keep @@ -161,11 +164,27 @@ public void downloadRemoteChanges(RealmConfiguration config) { if (config instanceof SyncConfiguration) { SyncConfiguration syncConfig = (SyncConfiguration) config; if (syncConfig.shouldWaitForInitialRemoteData()) { + // Create an empty schemaless Realm then wait for ROS to populate it + // with remote schema and potential data. + DynamicRealm emptyRealm = DynamicRealm.createSchemalessInstance(syncConfig); SyncSession session = SyncManager.getSession(syncConfig); try { session.downloadAllServerChanges(); } catch (InterruptedException e) { + // If an error happened while downloading initial data, we need to reset the file so we can + // download it again on the next attempt. + + // FIXME: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now. so delete may throw + // https://github.com/realm/realm-java/issues/5416 + try { + Realm.deleteRealm(config); + } catch (IllegalStateException exception) { + RealmLog.warn(exception); + } throw new DownloadingRealmInterruptedException(syncConfig, e); + } finally { + emptyRealm.close(); } } }