Initial work for unnamed backlink queries and @links.@count tests

This commit is contained in:
James Stone 2018-04-10 16:37:41 -07:00
parent c3a83f1866
commit 591aa9fee7
3 changed files with 58 additions and 15 deletions

View File

@ -95,3 +95,21 @@ Example:
// 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');
```
### 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) {
auto target_object_schema = schema.find(property.object_type);
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." + std::string(target_table->get_name()) + "." + property.link_origin_property_name;
std::string native_name = "@links." + target_object_schema->name + "." + property.link_origin_property_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();
DescriptorOrdering ordering;
parser::KeyPathMapping mapping;
mapping.set_backlink_class_prefix(ObjectStore::table_name_for_object_type(""));
alias_backlinks(mapping, realm);
parser::ParserResult result = parser::parse(query_string);

View File

@ -109,24 +109,49 @@ module.exports = {
let english = realm.create('Language', {name: 'English'});
let french = realm.create('Language', {name: 'French'});
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 denmark = realm.create('Country', {name: 'Denmark', languages: [danish, english]});
let france = realm.create('Country', {name: 'France', languages: [french, english]});
});
let languages = realm.objects('Language');
let spokenInThreeCountries = languages.filtered('spokenIn.@count == 3');
TestCase.assertEqual(spokenInThreeCountries.length, 1);
TestCase.assertEqual(spokenInThreeCountries[0].name, 'English');
let spokenInTwoCountries = languages.filtered('spokenIn.@count == 2');
TestCase.assertEqual(spokenInTwoCountries.length, 1);
TestCase.assertEqual(spokenInTwoCountries[0].name, 'French')
let spokenInOneCountry = languages.filtered('spokenIn.@count == 1');
TestCase.assertEqual(spokenInOneCountry.length, 1);
TestCase.assertEqual(spokenInOneCountry[0].name, 'Danish')
let languagesSpokenInCanada = languages.filtered('spokenIn.name ==[c] "canada"');
TestCase.assertEqual(languagesSpokenInCanada.length, 2);
TestCase.assertEqual(languagesSpokenInCanada[0].name, 'English');
TestCase.assertEqual(languagesSpokenInCanada[1].name, 'French');
{
let spokenInThreeCountries = languages.filtered('spokenIn.@count == 3');
TestCase.assertEqual(spokenInThreeCountries.length, 1);
TestCase.assertEqual(spokenInThreeCountries[0].name, 'English');
let spokenInTwoCountries = languages.filtered('spokenIn.@count == 2');
TestCase.assertEqual(spokenInTwoCountries.length, 1);
TestCase.assertEqual(spokenInTwoCountries[0].name, 'French')
let spokenInOneCountry = languages.filtered('spokenIn.@count == 1');
TestCase.assertEqual(spokenInOneCountry.length, 1);
TestCase.assertEqual(spokenInOneCountry[0].name, 'Danish')
let languagesSpokenInCanada = languages.filtered('spokenIn.name ==[c] "canada"');
TestCase.assertEqual(languagesSpokenInCanada.length, 2);
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() {