Skip to content

feat: add logical delete/forget API - #59

Merged
beinan merged 3 commits into
lance-format:mainfrom
dcfocus:codex/delete-forget-record
Jun 9, 2026
Merged

beinan merged 3 commits into
lance-format:mainfrom
dcfocus:codex/delete-forget-record

Conversation

@dcfocus

@dcfocus dcfocus commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add Context.delete()/forget() and PyO3 bindings for id or external_id deletes
  • Store deletes as internal tombstone records so default list/get/search hide forgotten rows
  • Add Rust/Python tests and docs for logical delete semantics

Testing

  • uv run --with maturin maturin develop
  • uv run --with pytest pytest python/tests/ -v
  • uv run --with pytest pytest tests/test_delete.py tests/test_search.py -v
  • uv run --with ruff ruff format --check python/
  • uv run --with ruff ruff check python/
  • uv run --with pyright pyright
  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test -p lance-context-core --lib
  • cargo test -p lance-context-core --doc

Note: broader local python/tests run has 4 pre-existing raw-fragment/compaction failures with MemWAL visibility (test_compaction..., test_persistence...). Focused delete tests and the exact workflow test path pass.

Closes #55

@beinan
beinan merged commit dab6332 into lance-format:main Jun 9, 2026
7 checks passed
@dcfocus
dcfocus deleted the codex/delete-forget-record branch June 27, 2026 19:10
beinan added a commit that referenced this pull request Jul 25, 2026
## The problem

`python-test.yml` ran `pytest python/tests/` with `working-directory:
python`, which resolves to **`python/python/tests/`** — a single stale
file with 3 tests. The real suite, **`python/tests/` (24 files, 179
tests)**, was never collected. pytest exits 0 on a
valid-but-nearly-empty directory, so this failed silently.

Git history dates the split: `python/python/tests/` last changed in #59;
`python/tests/` is current through #158. **~100 PRs merged ungated** —
rollout store, async wrappers, export, ingestion.

Two further layers of silencing, each sufficient on its own:
- Only bare `pytest` was installed, never the `[tests]` extra — so
`pytest-asyncio` was absent.
- No `asyncio_mode` anywhere, so the 12 `async def` tests were
collected, never awaited, and **passed without executing a line**.

The same path bug appeared three more times: the doctest step was a
permanent no-op (an `if` on a path that never exists, plus `|| echo`),
and `ruff format --check python/` / `ruff check python/` **never linted
the 24 test files**. `.codex/.../run_ci_checks.sh` pointed at a `rust/`
directory that does not exist, so `set -e` killed it before any Python
check ran.

## CI changes

- Pin collection with `testpaths = ["tests"]` so the ambiguous relative
path cannot resurface.
- `asyncio_mode = "strict"` + `--strict-markers --strict-config` — a
mistyped marker now fails instead of silently disarming a test.
- Install `[lance-python,tests]`; lint `.` instead of `python/`; make
the doctest step real.
- Point the helper script at the real `crates/` layout.

## Turning the suite on surfaced 19 real failures

**1. Embedded `RolloutStore` silently discarded every write.** This is
the significant one. #181 made `add` durable-but-not-visible, with
visibility driven by a server-side sweeper. The embedded path has no
sweeper, and `flush()` was never exposed to Python — so:

```python
s = RolloutStore.open(d)
s.add({'id':'a','rollout_id':'t1'})   # -> {'version': 2, 'ids': ['a'], 'count': 1}
s.list()                              # -> []   ... and still [] after reopen
```

Data acknowledged, then unreadable forever. This PR exposes `flush()`
through the facade, PyO3, and both sync and async Python wrappers.

**2. `test_search.py`'s `DummyInner` fake had drifted** from the real
binding signature (missing `include_binary`/`include_embedding`), so all
55 tests raised `TypeError` on contact with current code.

**3. Remote rollout tests** asserted read-your-write against a server
whose default flush interval is 30s. They now set
`ROLLOUT_FLUSH_INTERVAL_SECS=1` and poll — asserting the row *arrives*
rather than pinning to the interval.

The 3 stale tests are **moved** into `python/tests/` rather than dropped
(they cover `snapshot`/`fork`/`memory://` and still pass), and the
orphaned directory is removed so nothing can silently collect it again.

## Verification

- **182 passed, 2 skipped** (179 real + 3 rescued).
- `ruff format --check .`, `ruff check .`, `pyright` — all clean.
- `cargo fmt --all -- --check` and clippy clean on the changed crates.
- Confirmed the async tests now genuinely execute by injecting a failing
assertion and watching it fail — it would not have before this change.

## Note on scope

This is deliberately scoped to *making CI honest* plus the failures that
gating surfaced. It does **not** address: publish workflows having no
`needs:` on any test job (a release cut from a red `main` ships to
PyPI/crates.io), `cargo test` covering only 2 of 8 crates and `--lib`
only, or the `DummyInner` fake asserting hardcoded literals so RRF
ranking is still never exercised. Happy to file those separately.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

Add first-class delete/forget by stable record ID

2 participants