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

zql [2/n] - Hook Replicache up to ZQL #30

Merged
merged 3 commits into from
Mar 15, 2024
Merged

zql [2/n] - Hook Replicache up to ZQL #30

merged 3 commits into from
Mar 15, 2024

Conversation

tantaman
Copy link

Takes events from replicache.experimentalWatch and sends them to ZQL to maintain queries.

See replicache-context.test.ts for examples.

r.mutate.initE1({id: '5', str: 'y'}),
]);

expect(view.value).toEqual([{id: '5'}, {id: '4'}, {id: '3'}]);
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like we are copying the values here, to create objects with only the selected fields. I'm concerned about doing this for perf reasons. I would actually expect this test to fail, because of the extra str field in view.value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Possible I'm overthinking this. LMK what you think. FYI @arv

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's get the desired semantics first and measure the performance before we start making compromises.

r.mutate.initE1({id: '5', str: 'y'}),
]);

expect(view.value).toEqual([{id: '5'}, {id: '4'}, {id: '3'}]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's get the desired semantics first and measure the performance before we start making compromises.

src/zql/context/replicache-context.ts Show resolved Hide resolved
src/zql/context/replicache-context.ts Outdated Show resolved Hide resolved
Comment on lines +79 to +87
// This lookup of old was due to seeing `oldValue` values that did not match
// what was stored on the client when working on Repliear.
// `oldValue` not matching what is on the client is problematic
// if there are derived sources with orderings that depend on fields outside the id.
// E.g.,
// If a derived source is sorted by `modified_time`
// and the `oldValue` provided has a different `modified_time` we'll fail to remove
// the correct value from the derived source.
Copy link
Contributor

@arv arv Mar 12, 2024

Choose a reason for hiding this comment

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

I'm wondering what is going on here? We need to track this down. We made watch non experimental on main and if there is a serious bug like this maybe we should keep it experimental.

Copy link
Contributor

Choose a reason for hiding this comment

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

Dax has reported something like this to me, but he didn't ever have time to make a reproduction.

Looking at the code, neither Greg nor I can imagine any way this can happen. If you have a reproduction let's fix it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you very much for noticing/flagging this @arv - totally missed it.

Copy link
Author

Choose a reason for hiding this comment

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

This was several months ago that I saw the behavior. I'll see if I can put together a consistent repro though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this could happen when changes are batched. Perhaps there is some bug where multiple updates to the same key get smooshed together as one experimentalWatch update and the oldValue for some reason is not the one that's currently in Replicache, but one of the other ones that got smooshed together. Just a random guess.

Copy link
Contributor

Choose a reason for hiding this comment

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

Correction. watch is still experimental but I would still like to figure this out.

Copy link
Contributor

Choose a reason for hiding this comment

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

One potential issue here is that await tx.get('x') might not be === to await tx.get('x') because cache eviction

src/zql/context/replicache-context.ts Outdated Show resolved Hide resolved
src/zql/context/replicache-context.ts Outdated Show resolved Hide resolved
if (lVal === rVal) {
return 0;
}
if (lVal === null || lVal === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why include undefined?

Is it to support optional properties?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, Rails / Zod allows undefined for optional properties.

A user could request to sort on an optional property.

Screenshot 2024-03-13 at 9 40 37 PM

if (rVal === null || rVal === undefined) {
return 1;
}
if (lVal < rVal) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we detecting non comparable values at any layer. It would be good if it could be enforced by typescript since we want this to be forward compatible with zero.

Copy link
Author

Choose a reason for hiding this comment

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

We are not. We could do this in TypeScript in the orderBy method of EntityQuery. There we can exclude any field types that are not comparable.

src/zql/ivm/compare.ts Outdated Show resolved Hide resolved
@@ -120,12 +119,15 @@ export abstract class SetSource<T> implements Source<T> {
this._materialite.addDirtySource(this.#internal);
return this;
}

get(key: T): T | null {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use undefined for non present keys. That would be more consistent with everything else.

@tantaman tantaman changed the title Hook Replicache up to ZQL zql - Hook Replicache up to ZQL Mar 13, 2024
@tantaman tantaman changed the title zql - Hook Replicache up to ZQL zql [2/n] - Hook Replicache up to ZQL Mar 14, 2024
return this.#tree.get(key);
get(key: T): T | undefined {
const ret = this.#tree.get(key);
if (ret === null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see this is from a different layer... I guess it is fine to use null if it does not leak to any public APIs

Copy link
Author

Choose a reason for hiding this comment

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

We can pull in that other layer down the line. It is pretty much just a Treap and PersistentTreap.

Base automatically changed from mlaw/zql-ivm to main March 15, 2024 01:34
@tantaman tantaman merged commit 6b95485 into main Mar 15, 2024
4 checks passed
@tantaman tantaman deleted the mlaw/zql-repl branch March 15, 2024 01:58
}

#onReplicacheDiff = (changes: ExperimentalNoIndexDiff) => {
this.#materialite.tx(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if the materialite.tx should be around all of the diffs (for all of the source tables) for an underlying Replicache commit. I think achieving this would require a deeper integration with Replicache.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well actually since we dispatch all the diffs synchronously, I think we can achieve it without deeper integration, if we have a version of materialite.tx with explicit materialite.txStart/materialite.txEnd. If a tx is not open #onReplicacheDiff, we do materialite.txStart, and enqueue a microtask to do materialite.txEnd.

Copy link
Author

Choose a reason for hiding this comment

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

I'm wondering if the materialite.tx should be around all of the diffs (for all of the source tables) for an underlying Replicache commit.

yep, it should. Good catch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch. That might also explain some odd behaviors I'm seeing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants