Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2017 Realm Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.realm.annotations;


import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation used to describe the relationship between two Realm model classes as <b>Strong</b>.
* If this annotation is not used the relationship is implicitly <b>Weak</b>.
* <p>
* This annotation can only be applied to fields that reference other Realm model classes either
* using a differet reference or using a RealmList. It cannot be used on RealmLists containing
* primitive types nor on fields marked with {@link LinkingObjects}. The annotation processor will
* throw an error in that case.
* <p>
* The relationship is determined pr. field, i.e. it is possible for an object A to have both a Weak
* and Strong relationship with object B if A contains multiple fields that reference B.
* <p>
* A strong relationship between a parent object A and a child object B means B will automatically
* be deleted if A no longer holds a reference to B or if A is deleted itself. This is also known as
* cascading deletes.
* <p>
* It is possible for multiple different classes to have a strong reference to the same object.
* In that case the child object is only deleted automatically when the last strong parent reference
* is removed or all parent objects are deleted.
* <p>
* It is possible to create child objects (B) with no parents (A), these will only be deleted if
* an parent actually existed at some point, e.g. If A references B, then it is possible to
* create object B first and then later add the reference from A to B. B will only be
* automatically deleted when A either explicitly removes its reference or is deleted itself.
* <p>
* <b>Warning:</b><br>
* Some special corner cases exists for cyclic object graphs that are strongly connected:
* <ul>
* <li>
* {@code ( A -> B <-> C )}:<br>
* Object A contains a reference to B and C that in turn reference each other.
* If the reference between A and B is removed, then B and C are an "disconnected island".
* In this case neither B nor C will be deleted automatically and this must be done
* manually.
* </li>
* <li>
* {@code ( A <-> B )}:<br>
* Object A and Object B strongly references each other in isolation.
* If either A or B breaks the reference so the graph becomes {@code (A -> B)} or
* {@code (B -> A)}, then both A and B is deleted. It is possible to avoid this
* behaviour by adding an outside strong reference, so the graph becomes:
* {@code ( C - > A <-> B )}.
* </li>
* </ul>
* <p>
* Example:
* {@code
* public class Person extends RealmObject {
*
* public String name;
*
* // The ContactInfo object is deleted when the person is.
* \@StrongRelationship
* public ContactInfo contactInfo;
*
* // Is implicitly weak. The Dog objects will not be deleted when the person is.
* public RealmList<Dog> dogs = new RealmList<>();
* }
* }
* <p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface StrongRelationship {
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import io.realm.annotations.RealmField;
import io.realm.annotations.RealmNamingPolicy;
import io.realm.annotations.Required;
import io.realm.annotations.StrongRelationship;
import io.realm.processor.nameconverter.NameConverter;


Expand All @@ -65,7 +66,7 @@ public class ClassMetaData {
private final Set<Backlink> backlinks = new HashSet<Backlink>();
private final Set<RealmFieldElement> nullableFields = new HashSet<RealmFieldElement>(); // Set of fields which can be nullable
private final Set<RealmFieldElement> nullableValueListFields = new HashSet<RealmFieldElement>(); // Set of fields whose elements can be nullable

private final Set<VariableElement> strongReferences = new HashSet<>(); // Set of fields whose elements have strong references
private String packageName; // package name for model class.
private boolean hasDefaultConstructor; // True if model has a public no-arg constructor.
private VariableElement primaryKey; // Reference to field used as primary key, if any.
Expand Down Expand Up @@ -171,6 +172,10 @@ public Set<Backlink> getBacklinkFields() {
return Collections.unmodifiableSet(backlinks);
}

public boolean isStrongReference(VariableElement field) {
return strongReferences.contains(field);
}

public String getInternalGetter(String fieldName) {
return "realmGet$" + fieldName;
}
Expand Down Expand Up @@ -556,11 +561,20 @@ private boolean categorizeField(Element element) {
}
}

// Check that @RealmField is used on the correct fields.
if (field.getAnnotation(StrongRelationship.class) != null) {
if (!Utils.isRealmModel(field) && !Utils.isRealmModelList(field)) {
Utils.error("@RealmField is only allowed on fields that reference either other Realm model classes or lists of model classes.");
return false;
}
strongReferences.add(field);
}

if (field.getAnnotation(PrimaryKey.class) != null) {
if (!categorizePrimaryKeyField(field)) { return false; }
}

// @LinkingObjects cannot be @PrimaryKey or @Index.
// @LinkingObjects cannot be a Strong reference, @PrimaryKey or @Index.
if (field.getAnnotation(LinkingObjects.class) != null) {
// Do not add backlinks to fields list.
return categorizeBacklinkField(field);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -746,15 +746,15 @@ private void emitCreateExpectedObjectSchemaInfo(JavaWriter writer) throws IOExce
case OBJECT: {
String fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field);
String internalClassName = Utils.getReferencedTypeInternalClassNameStatement(fieldTypeQualifiedName, classCollection);
writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, %s)",
fieldName, internalClassName);
writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.OBJECT, %s, %s)",
fieldName, internalClassName, metadata.isStrongReference(field));
break;
}
case LIST: {
String genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field);
String internalClassName = Utils.getReferencedTypeInternalClassNameStatement(genericTypeQualifiedName, classCollection);
writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, %s)",
fieldName, internalClassName);
writer.emitStatement("builder.addPersistedLinkProperty(\"%s\", RealmFieldType.LIST, %s, %s)",
fieldName, internalClassName, metadata.isStrongReference(field));
break;
}
case INTEGER_LIST:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,8 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() {
builder.addPersistedProperty("columnDate", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED);
builder.addPersistedProperty("columnBinary", RealmFieldType.BINARY, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED);
builder.addPersistedProperty("columnMutableRealmInteger", RealmFieldType.INTEGER, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED);
builder.addPersistedLinkProperty("columnObject", RealmFieldType.OBJECT, "AllTypes");
builder.addPersistedLinkProperty("columnRealmList", RealmFieldType.LIST, "AllTypes");
builder.addPersistedLinkProperty("columnObject", RealmFieldType.OBJECT, "AllTypes", false);
builder.addPersistedLinkProperty("columnRealmList", RealmFieldType.LIST, "AllTypes", false);
builder.addPersistedValueListProperty("columnStringList", RealmFieldType.STRING_LIST, !Property.REQUIRED);
builder.addPersistedValueListProperty("columnBinaryList", RealmFieldType.BINARY_LIST, !Property.REQUIRED);
builder.addPersistedValueListProperty("columnBooleanList", RealmFieldType.BOOLEAN_LIST, !Property.REQUIRED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1671,7 +1671,7 @@ private static OsObjectSchemaInfo createExpectedObjectSchemaInfo() {
builder.addPersistedProperty("fieldDoubleNull", RealmFieldType.DOUBLE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED);
builder.addPersistedProperty("fieldDateNotNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, Property.REQUIRED);
builder.addPersistedProperty("fieldDateNull", RealmFieldType.DATE, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED);
builder.addPersistedLinkProperty("fieldObjectNull", RealmFieldType.OBJECT, "NullTypes");
builder.addPersistedLinkProperty("fieldObjectNull", RealmFieldType.OBJECT, "NullTypes", false);
builder.addPersistedValueListProperty("fieldStringListNotNull", RealmFieldType.STRING_LIST, Property.REQUIRED);
builder.addPersistedValueListProperty("fieldStringListNull", RealmFieldType.STRING_LIST, !Property.REQUIRED);
builder.addPersistedValueListProperty("fieldBinaryListNotNull", RealmFieldType.BINARY_LIST, Property.REQUIRED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ public void migration_backlinkedSourceFieldDoesntExist() throws ClassNotFoundExc
.build();
OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0)
.addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)
.addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksTarget")
.addPersistedLinkProperty("child", RealmFieldType.OBJECT, "BacklinksTarget", false)
.build();
Map<Class<? extends RealmModel>, OsObjectSchemaInfo> infoMap =
new HashMap<Class<? extends RealmModel>, OsObjectSchemaInfo>();
Expand Down Expand Up @@ -643,7 +643,7 @@ public void migration_backlinkedSourceFieldWrongType() {
OsObjectSchemaInfo sourceSchemaInfo = new OsObjectSchemaInfo.Builder("BacklinksSource", 2, 0)
.addPersistedProperty("name", RealmFieldType.STRING, !Property.PRIMARY_KEY, !Property.INDEXED, !Property.REQUIRED)
.addPersistedLinkProperty("child", RealmFieldType.OBJECT,
"BacklinksSource"/*"BacklinksTarget" is the original value*/)
"BacklinksSource"/*"BacklinksTarget" is the original value*/, false)
.build();
Map<Class<? extends RealmModel>, OsObjectSchemaInfo> infoMap =
new HashMap<Class<? extends RealmModel>, OsObjectSchemaInfo>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright 2018 Realm Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.realm;


import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import io.realm.entities.RelationshipChild;
import io.realm.entities.RelationshipParent;
import io.realm.rule.TestRealmConfigurationFactory;

import static org.junit.Assert.assertEquals;


/**
* Testing Strong/Weak leaks. This should already be tested in ObjectStore/Core, so just doing
* smoke test here for the most common scenarios.
*/
@RunWith(AndroidJUnit4.class)
public class RelationshipTests {

@Rule
public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory();

private RealmConfiguration realmConfig;
private Realm realm;

@Before
public void setUp() {
realmConfig = configFactory.createConfiguration();
realm = Realm.getInstance(realmConfig);
realm.beginTransaction();
}

@After
public void tearDown() {
if (realm != null && !realm.isClosed()) {
realm.cancelTransaction();
realm.close();
}
}

@Test
public void cascadeDeleteStrongObjectReference() {
RealmResults<RelationshipChild> children = realm.where(RelationshipChild.class).findAll();

// Create Strong relationship
RelationshipChild child = realm.createObject(RelationshipChild.class);
child.name = "Child";
RelationshipParent parent = realm.createObject(RelationshipParent.class);
parent.strongObjectRef = child;

// Delete parent
assertEquals(1, children.size());
parent.deleteFromRealm();
assertEquals(0, children.size());
}

@Test
public void dontCascadeDeleteWeakObjectReference() {
RealmResults<RelationshipChild> children = realm.where(RelationshipChild.class).findAll();

// Create Strong relationship
RelationshipChild child = realm.createObject(RelationshipChild.class);
child.name = "Child";
RelationshipParent parent = realm.createObject(RelationshipParent.class);
parent.weakObjectRef = child;

// Delete parent
assertEquals(1, children.size());
parent.deleteFromRealm();
assertEquals(1, children.size());
}

@Test
public void cascadeDeleteStrongListReference() {
int TEST_SIZE = 10;
RealmResults<RelationshipChild> children = realm.where(RelationshipChild.class).findAll();

// Create Strong relationship
RelationshipParent parent = realm.createObject(RelationshipParent.class);
for (int i = 0; i < TEST_SIZE; i++) {
parent.strongListRef.add(new RelationshipChild("child " + i));
}

// Delete parent
assertEquals(TEST_SIZE, children.size());
parent.deleteFromRealm();
assertEquals(0, children.size());
}

@Test
public void dontCascadeDeleteWeakListReference() {
int TEST_SIZE = 10;
RealmResults<RelationshipChild> children = realm.where(RelationshipChild.class).findAll();

// Create Strong relationship
RelationshipParent parent = realm.createObject(RelationshipParent.class);
for (int i = 0; i < TEST_SIZE; i++) {
parent.weakListRef.add(new RelationshipChild("child " + i));
}

// Delete parent
assertEquals(TEST_SIZE, children.size());
parent.deleteFromRealm();
assertEquals(TEST_SIZE, children.size());
}

@Test
public void cascadeWhenFinalStrongReferenceIsGone() {
RealmResults<RelationshipChild> children = realm.where(RelationshipChild.class).findAll();

// Create Strong relationship
RelationshipChild child = realm.createObject(RelationshipChild.class);
child.name = "Child";
RelationshipParent parent1 = realm.createObject(RelationshipParent.class);
parent1.strongObjectRef = child;
RelationshipParent parent2 = realm.createObject(RelationshipParent.class);
parent2.strongObjectRef = child;

assertEquals(1, children.size());
parent1.deleteFromRealm();
assertEquals(1, children.size());
parent2.deleteFromRealm();
assertEquals(0, children.size());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2018 Realm Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.realm.entities;

import io.realm.RealmObject;

public class RelationshipChild extends RealmObject {

public String name;

public RelationshipChild() {
}

public RelationshipChild(String name) {
this.name = name;
}
}
Loading