Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Queries using query paths on Mixed values returns inconsistent results #7587

Closed
elle-j opened this issue Apr 12, 2024 · 1 comment · Fixed by #7616
Closed

Queries using query paths on Mixed values returns inconsistent results #7587

elle-j opened this issue Apr 12, 2024 · 1 comment · Fixed by #7616
Assignees

Comments

@elle-j
Copy link
Contributor

elle-j commented Apr 12, 2024

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:

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:

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:

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

Copy link

sync-by-unito bot commented Apr 12, 2024

➤ PM Bot commented:

Jira ticket: RCORE-2081

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants