Uploaded image for project: 'Realm Core'
  1. Realm Core
  2. RCORE-2081

Queries using query paths on `Mixed` values returns inconsistent results

    • Type: Icon: Task Task
    • Resolution: Fixed
    • Priority: Icon: Minor - P4 Minor - P4
    • None
    • Affects Version/s: None
    • Component/s: None
    • None

      Expected results

      Note: Likely not highly important to be fixed for the first release of Collections in Mixed.

      When rebasing and thereby upgrading to Core 14.5.0, query tests that were passing in 14.0.1 (I know, apologies for the long version jump) are now failing.

      There may have been some changes to expected behavior rather than bugs, but most of these tests pertain to queries using null. The assumed-to-be expected results are seen in each test case.

      Actual Results

      These tests are written to be more reproducible:

      Using IN on dictionary keys:

      Unable to find source-code formatter for language: ts. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      it("dict - 'mixed.${key} IN $0, values'", function (this: RealmContext) {
        // Using 'double' here as a non-null value, but the behavior of
        // these tests seems to be the same for all non-null primitives.
        const dictOfPrim = { double: 2.5, nullValue: null };
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a dictionary" });
      
          // Create 3 objects with a list of prims as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: dictOfPrim });
          this.realm.create(MixedSchema.name, { mixed: dictOfPrim });
          this.realm.create(MixedSchema.name, { mixed: dictOfPrim });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        // ✅ Passes.
        let filtered = objects.filtered(`mixed.double IN $0`, [2.5]);
        expect(filtered.length).equals(3);
      
        //  Actual result: Returns 0 objects.
        filtered = objects.filtered(`mixed.double IN $0`, [2.5, null]);
        expect(filtered.length).equals(3);
      
        // ✅ Passes.
        filtered = objects.filtered(`mixed.nullValue IN $0`, [null]);
        expect(filtered.length).equals(3);
      
        //  Actual result: Returns 0 objects.
        filtered = objects.filtered(`mixed.nullValue IN $0`, [2.5, null]);
        expect(filtered.length).equals(3);
      });
      
      it("dict of dict - 'mixed.nestedDict.${key} IN values'", function (this: RealmContext) {
        // Using 'double' here as a non-null value, but the behavior of
        // these tests seems to be the same for all non-null primitives.
        const dictOfDictOfNull = { nestedDict: { double: 2.5, nullValue: null } };
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a dictionary" });
      
          // Create 3 objects with a list of prims as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        // ✅ Passes.
        let filtered = objects.filtered(`mixed.nestedDict.double IN $0`, [2.5]);
        expect(filtered.length).equals(3);
      
        //  Actual result: Returns 1 object (the object whose mixed field is a string).
        filtered = objects.filtered(`mixed.nestedDict.double IN $0`, [2.5, null]);
        expect(filtered.length).equals(3);
      
        //  Actual result: Returns 4 (all) objects.
        filtered = objects.filtered(`mixed.nestedDict.nullValue IN $0`, [null]);
        expect(filtered.length).equals(3);
      
        //  Actual result: Returns 1 object (the object whose mixed field is a string).
        filtered = objects.filtered(`mixed.nestedDict.nullValue IN $0`, [2.5, null]);
        expect(filtered.length).equals(3);
      });
      

      Lists - Matching null, using wildcards, using non-existent index:

      Unable to find source-code formatter for language: ts. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      it("list of list - 'mixed[0][0] == $0, null'", function (this: RealmContext) {
        const listOfListOfNull = [[null]];
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a list" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        const filtered = objects.filtered(`mixed[0][0] == $0`, null);
        expect(filtered.length).equals(3);
      });
      
      it("list of list - 'mixed[0][${nonExistentIndex}] == $0, null'", function (this: RealmContext) {
        const listOfListOfNull = [[null]];
        const nonExistentIndex = 1000;
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a list" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 1 object (the one where the mixed field is a string).
        const filtered = objects.filtered(`mixed[0][${nonExistentIndex}] == $0`, null);
        expect(filtered.length).equals(0);
      });
      
      it("list of list - 'mixed[0][*] == $0, null'", function (this: RealmContext) {
        const listOfListOfNull = [[null]];
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a list" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        const filtered = objects.filtered(`mixed[0][*] == $0`, null);
        expect(filtered.length).equals(3);
      });
      
      it("list of list - 'mixed[0][*].@type == 'null'", function (this: RealmContext) {
        const listOfListOfNull = [[null]];
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a list" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfListOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        const filtered = objects.filtered(`mixed[0][*].@type == 'null'`);
        expect(filtered.length).equals(3);
      });
      
      it("list - 'mixed[${nonExistentIndex}][*] == $0, null'", function (this: RealmContext) {
        const listOfNull = [null];
        const nonExistentIndex = 1000;
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a list" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: listOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfNull });
          this.realm.create(MixedSchema.name, { mixed: listOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        const filtered = objects.filtered(`mixed[${nonExistentIndex}][*] == $0`, null);
        expect(filtered.length).equals(0);
      });
      

      Dictionaries - Matching null, using wildcards, using non-existent index:

      Unable to find source-code formatter for language: ts. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      it("dict of dict - 'mixed.nestedDict.${key} == $0, null'", function (this: RealmContext) {
        const dictOfDictOfNull = { nestedDict: { nullValue: null } };
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a dictionary" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        let filtered = objects.filtered(`mixed['nestedDict']['nullValue'] == $0`, null);
        expect(filtered.length).equals(3);
      
        //  Actual result: Returns 4 (all) objects.
        filtered = objects.filtered(`mixed.nestedDict.nullValue == $0`, null);
        expect(filtered.length).equals(3);
      });
      
      it("dict of dict - 'mixed.nestedDict.${nonExistentKey} == $0, null'", function (this: RealmContext) {
        const dictOfDictOfNull = { nestedDict: { nullValue: null } };
        const nonExistentKey = "nonExistentKey";
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a dictionary" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        let filtered = objects.filtered(`mixed['nestedDict']['${nonExistentKey}'] == $0`, null);
        expect(filtered.length).equals(3); // Missing keys are treated as 'null'.
      
        //  Actual result: Returns 4 (all) objects.
        filtered = objects.filtered(`mixed.nestedDict.${nonExistentKey} == $0`, null);
        expect(filtered.length).equals(3); // Missing keys are treated as 'null'.
      });
      
      it("dict of dict - 'mixed.nestedDict[*] == $0, null'", function (this: RealmContext) {
        const dictOfDictOfNull = { nestedDict: { nullValue: null } };
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a dictionary" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        const filtered = objects.filtered(`mixed.nestedDict[*] == $0`, null);
        expect(filtered.length).equals(3);
      });
      
      it("dict of dict - 'mixed.nestedDict[*].@type == 'null''", function (this: RealmContext) {
        const dictOfDictOfNull = { nestedDict: { nullValue: null } };
      
        this.realm.write(() => {
          // Create 1 object with a string as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: "not a dictionary" });
      
          // Create 3 objects with a list with null as the mixed field.
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
          this.realm.create(MixedSchema.name, { mixed: dictOfDictOfNull });
        });
        const objects = this.realm.objects(MixedSchema.name);
        expect(objects.length).equals(4);
      
        //  Actual result: Returns 4 (all) objects.
        const filtered = objects.filtered(`mixed.nestedDict[*].@type == 'null'`);
        expect(filtered.length).equals(3);
      });
      

      Core version

      Core version: 14.5.0

            Assignee:
            jorgen.edelbo@mongodb.com Jørgen Edelbo
            Reporter:
            unitosyncbot Unito Sync Bot
            AD Core
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: