Merge pull request #1746 from realm/jas/backlink-update

Initial work for unnamed backlink queries and backlink count
This commit is contained in:
Kenneth Geisshirt 2018-05-07 14:28:31 +02:00 committed by GitHub
commit 0c73b06ce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 15 deletions

View File

@ -95,3 +95,21 @@ Example:
// Find contacts with friends above 21 in SF // Find contacts with friends above 21 in SF
let teens = realm.objects('Contact').filtered('SUBQUERY(friends, $friend, $friend.age > 21 AND $friend.city = "SF").@count > 0'); let teens = realm.objects('Contact').filtered('SUBQUERY(friends, $friend, $friend.age > 21 AND $friend.city = "SF").@count > 0');
``` ```
### Backlink queries
Other objects can link to an object and you can query on that releationship using the `@links` and `@links.ClassName.PropertyName` syntax.
If the relationship of the LinkingObject is named, use the name in the query just like you would use any other property for readability.
If the relationship is not named, you can use the `@links.ClassName.PropertyName` syntax where `ClassName.PropertyName` describes the forward relationship.
Example:
```JS
// Find contacts where someone from SF has them as friends
realm.objects('Contact').filtered('@links.Contact.friends.city == "SF"');
// Find contacts with no incomming links (across all linked properties)
let isolated = realm.objects('Contact').filtered('@links.@count == 0');
// Find contacts with no incoming friend links
let lonely = realm.objects('Contact').filtered('@links.Contact.friends.@count == 0');
```

View File

@ -160,8 +160,7 @@ inline void alias_backlinks(parser::KeyPathMapping &mapping, const realm::Shared
if (property.type == realm::PropertyType::LinkingObjects) { if (property.type == realm::PropertyType::LinkingObjects) {
auto target_object_schema = schema.find(property.object_type); auto target_object_schema = schema.find(property.object_type);
const TableRef table = ObjectStore::table_for_object_type(realm->read_group(), it->name); const TableRef table = ObjectStore::table_for_object_type(realm->read_group(), it->name);
const TableRef target_table = ObjectStore::table_for_object_type(realm->read_group(), target_object_schema->name); std::string native_name = "@links." + target_object_schema->name + "." + property.link_origin_property_name;
std::string native_name = "@links." + std::string(target_table->get_name()) + "." + property.link_origin_property_name;
mapping.add_mapping(table, property.name, native_name); mapping.add_mapping(table, property.name, native_name);
} }
} }
@ -181,6 +180,7 @@ typename T::Object ResultsClass<T>::create_filtered(ContextType ctx, const U &co
auto const &object_schema = collection.get_object_schema(); auto const &object_schema = collection.get_object_schema();
DescriptorOrdering ordering; DescriptorOrdering ordering;
parser::KeyPathMapping mapping; parser::KeyPathMapping mapping;
mapping.set_backlink_class_prefix(ObjectStore::table_name_for_object_type(""));
alias_backlinks(mapping, realm); alias_backlinks(mapping, realm);
parser::ParserResult result = parser::parse(query_string); parser::ParserResult result = parser::parse(query_string);

View File

@ -109,24 +109,49 @@ module.exports = {
let english = realm.create('Language', {name: 'English'}); let english = realm.create('Language', {name: 'English'});
let french = realm.create('Language', {name: 'French'}); let french = realm.create('Language', {name: 'French'});
let danish = realm.create('Language', {name: 'Danish'}); let danish = realm.create('Language', {name: 'Danish'});
let latin = realm.create('Language', {name: 'Latin'});
let canada = realm.create('Country', {name: 'Canada', languages: [english, french]}); let canada = realm.create('Country', {name: 'Canada', languages: [english, french]});
let denmark = realm.create('Country', {name: 'Denmark', languages: [danish, english]}); let denmark = realm.create('Country', {name: 'Denmark', languages: [danish, english]});
let france = realm.create('Country', {name: 'France', languages: [french, english]}); let france = realm.create('Country', {name: 'France', languages: [french, english]});
}); });
let languages = realm.objects('Language'); let languages = realm.objects('Language');
let spokenInThreeCountries = languages.filtered('spokenIn.@count == 3'); {
TestCase.assertEqual(spokenInThreeCountries.length, 1); let spokenInThreeCountries = languages.filtered('spokenIn.@count == 3');
TestCase.assertEqual(spokenInThreeCountries[0].name, 'English'); TestCase.assertEqual(spokenInThreeCountries.length, 1);
let spokenInTwoCountries = languages.filtered('spokenIn.@count == 2'); TestCase.assertEqual(spokenInThreeCountries[0].name, 'English');
TestCase.assertEqual(spokenInTwoCountries.length, 1); let spokenInTwoCountries = languages.filtered('spokenIn.@count == 2');
TestCase.assertEqual(spokenInTwoCountries[0].name, 'French') TestCase.assertEqual(spokenInTwoCountries.length, 1);
let spokenInOneCountry = languages.filtered('spokenIn.@count == 1'); TestCase.assertEqual(spokenInTwoCountries[0].name, 'French')
TestCase.assertEqual(spokenInOneCountry.length, 1); let spokenInOneCountry = languages.filtered('spokenIn.@count == 1');
TestCase.assertEqual(spokenInOneCountry[0].name, 'Danish') TestCase.assertEqual(spokenInOneCountry.length, 1);
let languagesSpokenInCanada = languages.filtered('spokenIn.name ==[c] "canada"'); TestCase.assertEqual(spokenInOneCountry[0].name, 'Danish')
TestCase.assertEqual(languagesSpokenInCanada.length, 2); let languagesSpokenInCanada = languages.filtered('spokenIn.name ==[c] "canada"');
TestCase.assertEqual(languagesSpokenInCanada[0].name, 'English'); TestCase.assertEqual(languagesSpokenInCanada.length, 2);
TestCase.assertEqual(languagesSpokenInCanada[1].name, 'French'); TestCase.assertEqual(languagesSpokenInCanada[0].name, 'English');
TestCase.assertEqual(languagesSpokenInCanada[1].name, 'French');
}
// check the same but using the unnamed relationship which is available to users
{
let spokenInThreeCountries = languages.filtered('@links.Country.languages.@count == 3');
TestCase.assertEqual(spokenInThreeCountries.length, 1);
TestCase.assertEqual(spokenInThreeCountries[0].name, 'English');
let spokenInTwoCountries = languages.filtered('@links.Country.languages.@count == 2');
TestCase.assertEqual(spokenInTwoCountries.length, 1);
TestCase.assertEqual(spokenInTwoCountries[0].name, 'French')
let spokenInOneCountry = languages.filtered('@links.Country.languages.@count == 1');
TestCase.assertEqual(spokenInOneCountry.length, 1);
TestCase.assertEqual(spokenInOneCountry[0].name, 'Danish')
let languagesSpokenInCanada = languages.filtered('@links.Country.languages.name ==[c] "canada"');
TestCase.assertEqual(languagesSpokenInCanada.length, 2);
TestCase.assertEqual(languagesSpokenInCanada[0].name, 'English');
TestCase.assertEqual(languagesSpokenInCanada[1].name, 'French');
}
let notSpokenInAnyCountry = languages.filtered('@links.@count == 0'); // no incoming links over any relationships to the object
TestCase.assertEqual(notSpokenInAnyCountry.length, 1);
TestCase.assertEqual(notSpokenInAnyCountry[0].name, 'Latin');
let notSpokenMethod2 = languages.filtered('@links.Country.languages.@count == 0'); // links of a specific relationship are 0
TestCase.assertEqual(notSpokenMethod2.length, 1);
TestCase.assertEqual(notSpokenMethod2[0].name, 'Latin');
}, },
testMethod: function() { testMethod: function() {