HarperFast / HarperFast/harper

Stale index entries for @indexed @computed attributes survive invalidation

Open
#2,370 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
2d 6h
Merged PRs (30d)
200

Description

## Summary

`updateIndices` derives **both** sides of an index diff from `propertyResolvers` — the stored record's value is never consulted for a computed attribute. After `invalidate()` reduces a row to its indexed-attribute stub, the computed resolver runs over a stub whose *input* fields are gone and yields `NaN`/`undefined`, so on the next write `remove(, id)` never happens: the superseded index entry lingers.

## Repro

```
type P @table @export { id: ID @primaryKey, price: Float, discount: Float,
salePrice: Float @computed(from: "price - (discount || 0)") @indexed }
```

1. `PUT {price: 100, discount: 40}` → index has `[60, id]`
2. `invalidate(id)`
3. `PUT {price: 200, discount: 0}` → index now has **both** `[60, id]` and `[200, id]`
4. `?salePrice=60` returns the row, whose actual `salePrice` is 200.

## Notes

- **Pre-existing** — independent of #2368 (before it, this path crashed outright per #2359; #2368 makes the row readable again, which makes this observable). The resolver-derived diff has never been able to see what the invalidation stub used to hold.
- Silent: no error anywhere, just stale query results until the row's index entries are rebuilt.
- A fix probably wants the invalidation path to remove computed index entries using the *pre-invalidation* record (available at `_writeInvalidate` time), or to tolerate stub-derived `NaN` by falling back to the stored index scan for removal.
- Surfaced by the #2368 pre-push review (rounds 1/5, adjudicated pre-existing).

Contributor guide

Open the contributing guide

Research direction

Start by tracing updateIndices, propertyResolvers, and the _writeInvalidate path described in the issue, then reproduce the PUT/invalidate/PUT sequence with the P example. Check how the pre-invalidation record and indexed entries are handled during removal. Done means the obsolete [60, id] entry is removed and querying salePrice=60 no longer returns the updated row.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.