Skip to content

Commit

Permalink
Access to RealmResults based on deleted RealmList
Browse files Browse the repository at this point in the history
* When the original RealmList is deleted, for most methods of
  RealmResults should just work without crash by just treat it like an
  empty RealmResults.
* RealmResults.where() throws IllegalStateExecption in this case.
* RealmResults.isValid() returns false in this case.

This is a temp fix, check realm/realm-core#1434
for more details.

Close #1945
  • Loading branch information
beeender committed Jan 22, 2016
1 parent fe2bae0 commit 25d19ef
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 30 deletions.
6 changes: 5 additions & 1 deletion realm/realm-jni/src/io_realm_internal_Util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_Util_nativeTestcase(
if (dotest)
ThrowException(env, BadVersion, "parm1", "parm2");
break;

case DeletedLinkViewException:
expect = "io.realm.internal.DeletedRealmListException: parm1";
if (dotest)
ThrowException(env, DeletedLinkViewException, "parm1", "parm2");
break;
}
if (dotest) {
return NULL;
Expand Down
58 changes: 40 additions & 18 deletions realm/realm-jni/src/io_realm_internal_tableview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,32 @@
using namespace realm;

// if you disable the validation, please remember to call sync_in_needed()
#define VIEW_VALID_AND_IN_SYNC(env, ptr) view_valid_and_in_sync(env, ptr)
#define VIEW_VALID_AND_IN_SYNC(env, ptr) (is_view_valid(env, ptr) && is_view_in_sync(env, ptr))

inline bool view_valid_and_in_sync(JNIEnv* env, jlong nativeViewPtr) {
bool valid = (TV(nativeViewPtr) != NULL);
if (valid) {
if (!TV(nativeViewPtr)->is_attached()) {
ThrowException(env, TableInvalid, "The Realm has been closed and is no longer accessible.");
return false;
}
TV(nativeViewPtr)->sync_if_needed();
inline bool is_view_valid(JNIEnv* env, jlong nativeViewPtr) {
if (TV(nativeViewPtr) == NULL) {
// Should never get here
ThrowException(env, FatalError, "Null pointer of table view.");
return false;
}
if (!TV(nativeViewPtr)->is_attached()) {
ThrowException(env, TableInvalid, "The Realm has been closed and is no longer accessible.");
return false;
}
return valid;
return true;
}

inline bool is_view_in_sync(JNIEnv* /*env*/, jlong nativeViewPtr) {
try {
TV(nativeViewPtr)->sync_if_needed();
} catch (realm::DeletedLinkView&) {
// FIXME: Temp fix for https://github.com/realm/realm-core/pull/1434
// Better solution would be core only throw the exception when really necessary, methods like size
// should just return 0.
return false;
}
return true;
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_createNativeTableView(
JNIEnv* env, jobject, jobject, jlong)
Expand Down Expand Up @@ -105,10 +117,16 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetSourceRowIndex
(JNIEnv *env, jobject, jlong nativeViewPtr, jlong rowIndex)
{
try {
if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr))
return 0;
if (!is_view_valid(env, nativeViewPtr)) {
return npos;
}
if (!is_view_in_sync(env, nativeViewPtr)) {
TR_ERR("The source LinkView created this TableView has been deleted.");
// Let it fall through. Since the size will return 0, an ArrayIndexOutOfBoundsException
// will be thrown in ROW_INDEX_VALID check.
}
if (!ROW_INDEX_VALID(env, TV(nativeViewPtr), rowIndex))
return 0;
return npos;
} CATCH_STD()
return TV(nativeViewPtr)->get_source_ndx(S(rowIndex)); // noexcept
}
Expand All @@ -117,7 +135,8 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetColumnCount
(JNIEnv *env, jobject, jlong nativeViewPtr)
{
try {
if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr))
// No need to sync here.
if (!is_view_valid(env, nativeViewPtr))
return 0;
} CATCH_STD()
return TV(nativeViewPtr)->get_column_count();
Expand All @@ -127,7 +146,8 @@ JNIEXPORT jstring JNICALL Java_io_realm_internal_TableView_nativeGetColumnName
(JNIEnv *env, jobject, jlong nativeViewPtr, jlong columnIndex)
{
try {
if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex))
// No need to sync here.
if (!is_view_valid(env, nativeViewPtr) || !COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex))
return NULL;
return to_jstring(env, TV(nativeViewPtr)->get_column_name( S(columnIndex)));
} CATCH_STD()
Expand All @@ -138,8 +158,9 @@ JNIEXPORT jlong JNICALL Java_io_realm_internal_TableView_nativeGetColumnIndex
(JNIEnv *env, jobject, jlong nativeViewPtr, jstring columnName)
{
try {
if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr))
return 0;
// No need to sync here.
if (!is_view_valid(env, nativeViewPtr))
return 0;

JStringAccessor columnName2(env, columnName); // throws
return to_jlong_or_not_found( TV(nativeViewPtr)->get_column_index(columnName2) ); // noexcept
Expand All @@ -151,7 +172,8 @@ JNIEXPORT jint JNICALL Java_io_realm_internal_TableView_nativeGetColumnType
(JNIEnv *env, jobject, jlong nativeViewPtr, jlong columnIndex)
{
try {
if (!VIEW_VALID_AND_IN_SYNC(env, nativeViewPtr) || !COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex))
// No need to sync here.
if (!is_view_valid(env, nativeViewPtr) || !COL_INDEX_VALID(env, TV(nativeViewPtr), columnIndex))
return 0;
} CATCH_STD()
return static_cast<int>( TV(nativeViewPtr)->get_column_type( S(columnIndex)) );
Expand Down
9 changes: 9 additions & 0 deletions realm/realm-jni/src/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ void ConvertException(JNIEnv* env, const char *file, int line)
ss << e.what() << " in " << file << " line " << line;
ThrowException(env, BadVersion, ss.str());
}
catch (realm::DeletedLinkView& e) {
ss << e.what() << " in " << file << " line " << line;
ThrowException(env, DeletedLinkViewException, ss.str());
}
catch (std::exception& e) {
ss << e.what() << " in " << file << " line " << line;
ThrowException(env, FatalError, ss.str());
Expand Down Expand Up @@ -152,6 +156,11 @@ void ThrowException(JNIEnv* env, ExceptionKind exception, const std::string& cla
jExceptionClass = env->FindClass("io/realm/internal/async/BadVersionException");
message = classStr;
break;

case DeletedLinkViewException:
jExceptionClass = env->FindClass("io/realm/internal/DeleteRealmListException");
message = classStr;
break;
}
if (jExceptionClass != NULL) {
env->ThrowNew(jExceptionClass, message.c_str());
Expand Down
3 changes: 2 additions & 1 deletion realm/realm-jni/src/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ enum ExceptionKind {
RuntimeError = 12,
RowInvalid = 13,
CrossTableLink = 15,
BadVersion = 16
BadVersion = 16,
DeletedLinkViewException = 17
// NOTE!!!!: Please also add test cases to Util.java when introducing a new exception kind.
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,9 @@ public void testMinValueForAllNullRows() {
TestHelper.populateAllNullRowsForNumericTesting(testRealm);

RealmResults<NullTypes> results = testRealm.where(NullTypes.class).findAll();
assertNull(results.max(NullTypes.FIELD_INTEGER_NULL));
assertNull(results.max(NullTypes.FIELD_FLOAT_NULL));
assertNull(results.max(NullTypes.FIELD_DOUBLE_NULL));
assertNull(results.min(NullTypes.FIELD_INTEGER_NULL));
assertNull(results.min(NullTypes.FIELD_FLOAT_NULL));
assertNull(results.min(NullTypes.FIELD_DOUBLE_NULL));
assertNull(results.maxDate(NullTypes.FIELD_DATE_NULL));
}

Expand Down Expand Up @@ -1146,4 +1146,100 @@ public void testComparisons() {
assertEquals(10, testRealm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_LONG, -1).findAll().size());
assertEquals(10, testRealm.where(AllTypes.class).lessThan(AllTypes.FIELD_LONG, 0).findAll().size());
}

private RealmResults<Dog> populateRealmResultsOnDeletedLinkView() {
testRealm.beginTransaction();
Owner owner = testRealm.createObject(Owner.class);
for (int i = 0; i < 10; i++) {
Dog dog = new Dog();
dog.setName("name_" + i);
dog.setOwner(owner);
owner.getDogs().add(dog);
}
testRealm.commitTransaction();


RealmResults<Dog> dogs = owner.getDogs().where().equalTo(Dog.FIELD_NAME, "name_0").findAll();
//dogs = dogs.where().findFirst().getOwner().getDogs().where().equalTo(Dog.FIELD_NAME, "name_0").findAll();

testRealm.beginTransaction();
owner.removeFromRealm();
testRealm.commitTransaction();
return dogs;
}

public void testisValidResultsBuiltOnDeletedLinkView() {
assertEquals(false, populateRealmResultsOnDeletedLinkView().isValid());
}

public void testSizeResultsBuiltOnDeletedLinkView() {
assertEquals(0, populateRealmResultsOnDeletedLinkView().size());
}

public void testFirstResultsBuiltOnDeletedLinkView() {
try {
populateRealmResultsOnDeletedLinkView().first();
} catch (ArrayIndexOutOfBoundsException ignored) {
}
}

public void testLastResultsBuiltOnDeletedLinkView() {
try {
populateRealmResultsOnDeletedLinkView().last();
} catch (ArrayIndexOutOfBoundsException ignored) {
}
}

public void testSumResultsBuiltOnDeletedLinkView() {
RealmResults<Dog> dogs = populateRealmResultsOnDeletedLinkView();
assertEquals(0, dogs.sum(Dog.FIELD_AGE).intValue());
assertEquals(0f, dogs.sum(Dog.FIELD_HEIGHT).floatValue(), 0f);
assertEquals(0d, dogs.sum(Dog.FIELD_WEIGHT).doubleValue(), 0d);
}

public void testAverageResultsBuiltOnDeletedLinkView() {
RealmResults<Dog> dogs = populateRealmResultsOnDeletedLinkView();
assertEquals(0d, dogs.average(Dog.FIELD_AGE), 0d);
assertEquals(0d, dogs.average(Dog.FIELD_HEIGHT), 0d);
assertEquals(0d, dogs.average(Dog.FIELD_WEIGHT), 0d);
}

public void testClearResultsBuiltOnDeletedLinkView() {
RealmResults<Dog> dogs = populateRealmResultsOnDeletedLinkView();
testRealm.beginTransaction();
dogs.clear();
assertEquals(0, dogs.size());
testRealm.commitTransaction();
}

public void testMaxResultsBuiltOnDeletedLinkView() {
RealmResults<Dog> dogs = populateRealmResultsOnDeletedLinkView();
assertNull(dogs.max(Dog.FIELD_AGE));
assertNull(dogs.max(Dog.FIELD_HEIGHT));
assertNull(dogs.max(Dog.FIELD_WEIGHT));
}

public void testMaxDateResultsBuiltOnDeletedLinkView() {
assertEquals(null, populateRealmResultsOnDeletedLinkView().maxDate(Dog.FIELD_BIRTHDAY));
}

public void testMinResultsBuiltOnDeletedLinkView() {
RealmResults<Dog> dogs = populateRealmResultsOnDeletedLinkView();
assertNull(dogs.min(Dog.FIELD_AGE));
assertNull(dogs.min(Dog.FIELD_HEIGHT));
assertNull(dogs.min(Dog.FIELD_WEIGHT));
}

public void testMinDateResultsBuiltOnDeletedLinkView() {
assertEquals(null, populateRealmResultsOnDeletedLinkView().minDate(Dog.FIELD_BIRTHDAY));
}

public void testWhereResultsBuiltOnDeletedLinkView() {
try {
populateRealmResultsOnDeletedLinkView().where();
fail();
} catch (IllegalStateException e) {
assertEquals("The RealmList which this RealmResults is created on has been deleted.", e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -1827,8 +1826,8 @@ public void onChange() {
// Check if cat has been added to the realmObjects in case of the behaviour of getCat changes
for (WeakReference<RealmObject> weakReference : realm.handlerController.realmObjects.keySet()) {
if (weakReference.get() == cat) {
foundKey = true;
break;
foundKey = true;
break;
}
}
assertTrue(foundKey);
Expand Down Expand Up @@ -1890,4 +1889,58 @@ public void onChange() {

finishedLatch.await();
}

// Build a RealmResults from a RealmList, and delete the RealmList. Test the behavior of ChangeListener on the
// "invalid" RealmResults.
public void test_changeListener_onResultsBuiltOnDeletedLinkView() {
handler.post(new Runnable() {
@Override
public void run() {
realm = Realm.getInstance(configuration);
realm.beginTransaction();
AllTypes allTypes = realm.createObject(AllTypes.class);
for (int i = 0; i < 10; i++) {
Dog dog = new Dog();
dog.setName("name_" + i);
allTypes.getColumnRealmList().add(dog);
}
realm.commitTransaction();

final RealmResults<Dog> dogs =
allTypes.getColumnRealmList().where().equalTo(Dog.FIELD_NAME, "name_0").findAll();
dogs.addChangeListener(new RealmChangeListener() {
@Override
public void onChange() {
if (typebasedCommitInvocations.getAndIncrement() == 0) {
assertFalse(dogs.isValid());
assertEquals(0, dogs.size());
} else {
fail("This listener should only be called once.");
}
}
});

// Trigger the listener at the first time.
realm.beginTransaction();
allTypes.removeFromRealm();
realm.commitTransaction();

// Try to trigger the listener second time.
realm.beginTransaction();
realm.commitTransaction();

// Close the realm and finish the test. This needs to follow the REALM_CHANGED in the queue.
handler.post(new Runnable() {
@Override
public void run() {
realm.close();
signalTestFinished.countDown();
}
});
}
});

TestHelper.awaitOrFail(signalTestFinished);
assertEquals(1, typebasedCommitInvocations.get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@

public class Dog extends RealmObject {

public static final String FIELD_NAME = "name";
public static final String FIELD_AGE = "age";
public static final String FIELD_HEIGHT = "height";
public static final String FIELD_WEIGHT = "weight";
public static final String FIELD_BIRTHDAY = "birthday";

@Index
private String name;
private long age;
Expand Down
Loading

0 comments on commit 25d19ef

Please sign in to comment.