diff --git a/docs/tutorials/query-language.md b/docs/tutorials/query-language.md index 57cb0b9a..6def6a3a 100644 --- a/docs/tutorials/query-language.md +++ b/docs/tutorials/query-language.md @@ -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'); +``` diff --git a/src/js_results.hpp b/src/js_results.hpp index e9f73294..5e11c94a 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -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::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); diff --git a/tests/js/linkingobjects-tests.js b/tests/js/linkingobjects-tests.js index 520513a4..a7e6fb6d 100644 --- a/tests/js/linkingobjects-tests.js +++ b/tests/js/linkingobjects-tests.js @@ -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() {