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

RJS-1413: Add support for a logical counter as a presentation data type #6694

Merged
merged 65 commits into from
Jun 12, 2024

Conversation

elle-j
Copy link
Contributor

@elle-j elle-j commented May 31, 2024

What, How & Why?

A Counter class and counter presentation data type have been added, representing a logical counter.

Note that counter types are not supported as:

  • Mixed values
  • Primary keys
  • Inside collections
  • Query arguments for placeholders (use Counter.value)

Usage

Valid property schema:

// Shorthand
"counter"
"counter?"

// Object form
{
  type: "int",
  presentation: "counter",
  optional: <true/false/undefined>
}

Creating an object with a counter:

realm.write(() => {
  realm.create(MyObject, { _id: "123", counter: 0 });
});

Updating the count:

realm.write(() => {
  object.counter.increment();
  object.counter.value; // 1
  object.counter.decrement(2);
  object.counter.value; // -1
  object.counter.set(100);
  object.counter.value; // 100
});

Creating a counter from a previously null value:

realm.write(() => {
  realm.create(MyObject, { _id: "123", counter: 0 }, UpdateMode.Modified);
});

Notes

  • No implicit coercion to number:
    • We decided not to support implicit coercion into number via valueOf partly due to TypeScript not supporting it.
  • Potential future reference
    • We decided to remove support for collections of counters.
      • If this becomes relevant in the future, this is the commit that removed related implementation and tests.

Test overview

Click to expand
  Counter
    create and access
      via 'realm.create()'
        ✓ can create and access (input: number)
        ✓ can create and access (input: Counter)
        ✓ can create and access (input: default value)
        ✓ can create nullable counter with int or null
        ✓ returns different reference for each access
    update
      Counter.value
        ✓ increments
        ✓ decrements
        ✓ sets
      Realm object counter property
        ✓ updates nullable counter from int -> null -> int via setter
        ✓ updates nullable counter from int -> null -> int via UpdateMode: modified
        ✓ updates nullable counter from int -> null -> int via UpdateMode: all
    filtering
      ✓ filters objects with counters
    Realm.schema
      ✓ includes `presentation: 'counter'` in the canonical property schema
    invalid operations
      ✓ throws when calling increment with non-integer
      ✓ throws when calling decrement with non-integer
      ✓ throws when calling set with non-integer
      ✓ throws when setting 'Counter.value' directly
      ✓ throws when updating outside write transaction
      ✓ throws when setting a non-nullable counter via setter
      ✓ throws when setting a non-nullable counter via UpdateMode: modified
      ✓ throws when setting a non-nullable counter via UpdateMode: all
      ✓ throws when setting a nullable counter from number -> number via setter
      ✓ throws when setting a nullable counter from number -> number via UpdateMode: modified
      ✓ throws when setting a nullable counter from number -> number via UpdateMode: all
      ✓ throws when setting an int property to a counter
      ✓ throws when getting the count on an invalidated obj
      ✓ throws when setting a mixed to a counter via object creation
      ✓ throws when setting a mixed to a counter via object setter
      ✓ throws when adding a counter to mixed collections via object creation
      ✓ throws when adding a counter to mixed collections via setters
      ✓ throws when using counter as query argument

  normalizePropertySchema
    Shorthand notation
      Valid combinations
        'counter'
          ✔ normalizes 'counter'
          ✔ normalizes 'counter?'
      Invalid shorthand notation
        ✔ throws when normalizing 'counter[]'
        ✔ throws when normalizing 'counter?[]'
        ✔ throws when normalizing 'counter{}'
        ✔ throws when normalizing 'counter?{}'
        ✔ throws when normalizing 'counter<>'
        ✔ throws when normalizing 'counter?<>'
    Object notation
      Valid object notation
        'counter'
          ✔ normalizes { type: 'int', presentation: 'counter' }
          ✔ normalizes { type: 'int', presentation: 'counter', optional: false }
          ✔ normalizes { type: 'int', presentation: 'counter', optional: true }
      Invalid object notation
        Mixing shorthand inside object notation
          ✔ throws when normalizing { type: 'counter' }
          ✔ throws when normalizing { type: 'list', objectType: 'counter?' }
        'counter' & collection combinations
          ✔ throws when normalizing { type: 'list', objectType: 'int', presentation: 'counter' }
          ✔ throws when normalizing { type: 'list', objectType: 'int', presentation: 'counter', optional: false }
          ✔ throws when normalizing { type: 'list', objectType: 'int', presentation: 'counter', optional: true }
          ✔ throws when normalizing { type: 'dictionary', objectType: 'int', presentation: 'counter' }
          ✔ throws when normalizing { type: 'dictionary', objectType: 'int', presentation: 'counter', optional: false }
          ✔ throws when normalizing { type: 'dictionary', objectType: 'int', presentation: 'counter', optional: true }
          ✔ throws when normalizing { type: 'set', objectType: 'int', presentation: 'counter' }
          ✔ throws when normalizing { type: 'set', objectType: 'int', presentation: 'counter', optional: false }
          ✔ throws when normalizing { type: 'set', objectType: 'int', presentation: 'counter', optional: true }
        Indexed and primary key combinations
          ✔ throws when normalizing { type: 'int', presentation: 'counter' } (primary key)

This closes #4089

☑️ ToDos

  • 📝 Changelog entry
  • 🚦 Tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a bit more docs onto this Counter class as we don't have it yet for the MDB docs. Please see if you think something should be reformulated/added/removed, etc.

Comment on lines +77 to +86
/** @internal */
constructor(realm: Realm, obj: binding.Obj, columnKey: binding.ColKey) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. Despite @internal, the constructors still show up in the API ref docs (for other classes as well).

Copy link
Member

@kraenhansen kraenhansen Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit weird 🤔 Perhaps there's (still) something misconfigured in the way we generate docs then.

Comment on lines -54 to +61
* If an existing object exists, only properties where the value has actually
* changed will be updated. This improves notifications and server side
* performance but also have implications for how changes across devices are
* merged. For most use cases, the behavior will match the intuitive behavior
* of how changes should be merged, but if updating an entire object is
* considered an atomic operation, this mode should not be used.
* If an existing object exists (determined by a matching primary key), only
* properties where the value has actually changed will be updated. This improves
* notifications and server side performance but also have implications for how
* changes across devices are merged. For most use cases, the behavior will match
* the intuitive behavior of how changes should be merged, but if updating an
* entire object is considered an atomic operation, this mode should not be used.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only change here is adding (determined by a matching primary key).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cred @nirinchev


/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- We define these once to avoid using "any" through the code */
export type AnyCollection = Collection<any, any, any, any, any>;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- We define these once to avoid using "any" through the code */
export type AnyDictionary = Dictionary<any>;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- We define these once to avoid using "any" through the code */
export type AnyList = List<any>;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- We define these once to avoid using "any" through the code */
export type AnySet = RealmSet<any>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also adding an unmanaged set (as an array), which I thought we had already done.

},
"Cannot use shorthand '[]' and '?' in 'type' or 'objectType' when defining property objects",
);
describe("'counter'", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant

"Cannot use shorthand '?' in 'type' or 'objectType' when defining property objects",
);

itThrowsWhenNormalizing(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant

"Cannot use shorthand 'counter' in 'type' or 'objectType' when defining property objects. To use presentation types such as 'counter', use the field 'presentation'",
);

itThrowsWhenNormalizing(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant

"Primary keys must always be indexed.",
{ isPrimaryKey: true },
);
describe("'counter' & collection combinations", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant

{ isPrimaryKey: true },
);

itThrowsWhenNormalizing(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant

@elle-j elle-j marked this pull request as ready for review May 31, 2024 09:36
Copy link
Member

@kneth kneth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the the introduction of the "presentation" concept has added to the complexity of implementing Counters. The concept is interesting, and I am looking forward on how we can use it in the future.

integration-tests/tests/src/tests/counter.ts Show resolved Hide resolved
@elle-j
Copy link
Contributor Author

elle-j commented May 31, 2024

RC release train will take off now 🚋

@elle-j elle-j requested review from kraenhansen and kneth June 10, 2024 05:35
Copy link
Member

@kneth kneth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work. As we have discussed the PR before the RC, I don't have any further comments (except a suggestion to link to the documentation).

Let's get it merged and released!

CHANGELOG.md Show resolved Hide resolved
elle-j and others added 26 commits June 12, 2024 10:23
Co-authored-by: Kræn Hansen <kraen.hansen@mongodb.com>
Co-authored-by: elle-j <elle-j@users.noreply.github.com>
Co-authored-by: elle-j <elle-j@users.noreply.github.com>
Co-authored-by: Kenneth Geisshirt <kenneth.geisshirt@mongodb.com>
Co-authored-by: Kræn Hansen <kraen.hansen@mongodb.com>
@elle-j elle-j merged commit f034f92 into main Jun 12, 2024
33 checks passed
@elle-j elle-j deleted the lj/counter-data-type branch June 12, 2024 10:29
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for counters
4 participants